Death to Field Arrays!

Mediacurrent Team
Sep
04
2014

Death to Field Arrays!

Entity Metadata Wrapper

Calling all Drupal Devs: Write better code with EntityMetadataWrapper! Have you ever...

  • Wondered which is the "right" language code (e.g., LANUGAGE_NONE) to use when accessing entity field data?
  • Been told that field_get_items() is the "right" way to access entity data but find it cumbersome?
  • Spent hours determining how to set the value of a complex field type, like a "link" field?

Well, this short article solves these woes and more! If used properly, EntityMetadataWrapper has the power to transform your ugly and fragile custom entity handling into readable, robust lines, and save you time to boot. So I'm glad you stopped by! Please read on...

  1. What is EMW?
  2. Basic Usage
  3. Common Mistakes
  4. Using EMW to check for empty field data
  5. Advanced Tips and Tricks
  6. Closing thoughts

 

 

Back to top

What is EMW?

The Entity API module provides EntityMetadataWrapper and friends to simplify the accessing and manipulating of field data. By implementing some PHP magic methods (__get() and __set(), among others) and certain PHP interfaces, EntityMetadataWrapper (or rather, its subclasses) allows you to write code that is more efficient, reliable, and readable.

EMW works with all entity types, from nodes to Commerce Orders to custom-defined entities. As for field data, it works well with all core field types and with most contrib field types: those that implement hook_entity_property_info() to expose their FieldAPI get/set controller methods.

 

 

Back to top

Basic Usage

First step, create the wrapper.

// You need the entity type and entity id.
$type    = 'node';
$nid     = 123;
$wrapper = entity_metadata_wrapper($type, $nid);

// You can also pass in a fully- or partially-loaded entity object instead of the id.
// Only do this if you're handed the object; i.e., don't bother loading the object just to pass into EMW.
$node    = node_load($nid); // Node loaded somewhere before your code.
$wrapper = entity_metadata_wrapper($type, $node);

Get the entity object back out of the wrapper.

$wrapper = entity_metadata_wrapper('node', $nid);
$node    = $wrapper->value();

Working with Text field data.

// OLD & BUSTED: Field API arrays
$l = $node->langugage;
$old_value = $node->field_my_text[$l][0]['value'];
$node->field_my_text[$l][0]['value'] = 'new value';
 
// New Hotness: EMW
$old_value = $wrapper->field_my_text->value();
$wrapper->field_my_text = 'new value';

Working with Longtext field data.

$wrapper->body->value(); // Array of: value, safe_value, format, and optionally summary + safe_summary.
$wrapper->body->value->value();  // Filtered value.
$wrapper->body->value->raw();    // Unfiltered value.
$wrapper->body->format->value(); // The selected text format.
$wrapper->body->value = 'new value';

Working with Link field data.

$wrapper->field_my_link->value(); // Array of: url, title, attributes.
$wrapper->field_my_link->url->value();   // Hyperlink destination.
$wrapper->field_my_link->title->value(); // Hyperlink text.
$wrapper->field_my_link->attributes->value(); // Array of: target, title, etc.
$wrapper->field_my_link->url   = 'http://mediacurrent.com';
$wrapper->field_my_link->title = 'Do Drupal Right';

Working with fields of a referenced entity. Chaining is a really neat part of EMW. Consider a Commerce Line Item entity which has a reference to its Commerce Order:

// Display "Order submitted by jane.doe".
$line_item = entity_metadata_wrapper('commerce_line_item', $line_item_id);
$name = $line_item->order->owner->label();
echo t('Order submitted by %label', array('%label' => $name));
...
// Display the second Line Item's date of creation.
$order = entity_metadata_wrapper('commerce_order', $order_id);
$label = $order->commerce_line_items[1]->label();
$date  = $order->commerce_line_items[1]->created->value();
echo t('%label created on %date', array(
  '%label' => $label,
  '%date'  => format_date($date),
  ));

Deleting a value.

$wrapper->field_foobar = NULL;

Looping over multi-value field data. Let's again consider a Commerce scenario. Say we need to perform logic against each Line Item of an Order:

$order = entity_metadata_wrapper('commerce_order', $order_id);
// Use count() to gracefully handle emptyset; this isn't required.
if (0 == $order->commerce_line_items->count()) {
  echo t('Order has no line items!');
  return;
}
foreach ($order->commerce_line_items as $line_item) {
  echo t('Processing Line Item %label', array('%label' => $line_item->label()));
  ...
}

Note that the d.o documentation demonstrates using the getIterator() method for looping. This is unncecessary; the method gets called automagically and need not be called explicitly except in rare cases.

 

 

Back to top

Common Mistakes

Loading the entity object just for passing into EMW. Unless you already have the pre-populated entity, it's more efficient to just pass the entity id to entity_metadata_wrapper(); EMW will lazy load the field and property data that it needs, hoorah! So much magic.

// Unnecessary node load...
$node    = node_load($nid);
$wrapper = entity_metadata_wrapper('node', $node);
 
// More efficient (if the object isn't handed to you)...
$wrapper = entity_metadata_wrapper('node', $nid);

Using set(), get(), offsetSet(), and offsetGet(). While these methods must exist under the hood to satisfy EMW's implementation of the ArrayAccess interface, you rarely need to use them. Improve your code readability and WOW your code reviewers by using the PHP magic methods that EMW has implemented! Disregard the d.o documentation that shows examples of using these methods.

// Instead of set() to assign new field values...
$wrapper->field_foobar->set('new value');
 
// Let the PHP magic method do its work!
$wrapper->field_foobar = 'new value';
 
// And Instead of offsetGet()/offsetSet() to access multi-value field data...
$old_value = $wrapper->field_foobar->offsetGet(2);
$wrapper->field_foobar->offsetSet(2, $new_value);
 
// You can treat that field like an array!
$old_value = $wrapper->field_foobar[2]->value();
$wrapper->field_foobar[2] = $new_value;
$wrapper->field_foobar[]  = $additional_value;

Saving the entity with node_save(), taxonomy_term_save(), etc. instead of the EMW save() method. Fun fact: both EMW and standard entity controllers include save() methods, so you can call e.g. $node->save() on properly instantiated entity objects and wrappers.

// If you don't already have the node object, don't bother loading it just to save...
$wrapper->field_foobar = 'new value';
$node = $wrapper->value();
node_save($node);
 
// Easier and prettier...
$wrapper->field_foobar = 'new value';
$wrapper->save();

Forgetting that modifications to the wrapper likewise affect the passed entity object! Because objects are passed by reference in PHP, this relationship holds when the entity object is passed into the wrapper or the entity object is gotten via the wrapper.

// Changes to the wrapper affect its entity object.
$node->field_foobar[$l][0]['value'] = 'foo';
$wrapper = entity_metadata_wrapper('node', $node);
$wrapper->field_foobar = 'bar';
return $node->field_foobar[$l][0]['value']; // Returns 'bar'.

And vice versa...

// Changes to the entity object affect its wrapper.
$wrapper = entity_metadata_wrapper('node', $nid);
$wrapper->field_foobar = 'bar';
$node = $wrapper->value();
$node->field_foobar[$l][0]['value'] = 'foo';
return $wrapper->field_foobar->value(); // Returns 'foo'.

 

 

Back to top

Using EMW to check for empty field data

Beware using EMW to check for empty field data. Let's say the "page" content type has field_foobar defined in its field configuration. Then the following will never* change, no matter whether a given page node actually has a value for field_foobar.

isset($wrapper->field_foobar); // Always TRUE
empty($wrapper->field_foobar); // Always FALSE

*Exception to the rule: Entities not loaded via the API (e.g., during programmatic creation, cached entity hydration, etc.) may lack field members and cause EMW to report them as missing.

Checking for empty textfields.

$value = $wrapper->field_my_text->value();
if (isset($value)) {
  // Entity has a non-null value for field_my_text, but it might be 0 or empty string.
}
if (!empty($value)) {
  // Entity has a value for field_my_text, and it is not 0, empty string, etc.
}

Checking for empty entity references.

// If efficiency is primary, use raw() to only load the referenced entity's ID.
// However, you can't assume the referenced entity actually exists.
$value = $wrapper->field_my_reference->raw();
 
// Using value() is less efficient because it loads all the entity data
// But it tells you whether the referenced entity actually exists.
$value = $wrapper->field_my_reference->value();
 
// Now use isset/!empty($value) as above.

Checking for empty fields of a referenced entity. Things get tricky here.

// First, make a shortcut variable.
$reference = $wrapper->field_my_reference; // An EMW object.
 
// Next, find out whether the reference is specified and exists.
// See above on using value() vs. raw() on entity references.
 
// Finally, follow the same "field is empty" checks as above. E.g.,
$value = $reference->field_foobar->value();
if (empty($value)) {
  $field_info = $reference->field_foobar->info();
  echo t("No value specified for %label"), array('%label' => $field_info['label']));
}
 
// For the curious, note this EMW output when the wrapper points to a
// non-existant entity or the value is not set on a reference field.
isset($wrapper->title);        // TRUE for any entity property.
isset($wrapper->field_foobar); // FALSE for any bundle field.
$wrapper->title->value();      // PHP fatal error for any field or property.

 

 

Back to top

Advanced Tips and Tricks

value() versus raw(): the differences between these methods vary by EMW class and by field type.

  • Wrapped entities (EntityDrupalWrapper instances): No difference. Both value() and raw() return the fully-loaded entity object.
  • Entity reference fields: value() returns the loaded entity object while raw() returns the entity id.
  • Unfiltered text fields: No difference. The two methods return identical output.
  • Filtered text fields: raw() returns the unfiltered user input while value() first passes the input through the field's selected text format.

Other important EMW methods: A smattering of the various EMW functions I use regularly. Note that there exists a wealth of additional useful methods, and I recommend exploring the API documentation on DrupalContrib to become familiar with it.

  • label(): For most entity types, label() simply returns the title property. For user entities, it returns the username property. And if using the realname module, it returns the user's name as defined by that module's configuration. For Commerce Orders, it returns "Order order id". The bottom line: In most cases, use label() as a simpler, prettier, more canonical version of $wrapper->title->value().
  • getIdentifier(): instead of $node_wrapper->nid->value(), $user_wrapper->uid->value(), $order_wrapper->order_id->value(), etc.
  • getPropertyInfo(): An invaluable debugging tool, this method outputs all the properties and fields that EMW knows about. Trying to access any item not in the list reported by getPropertyInfo() will result in a PHP exception, even if the item exists on the loaded entity object. getPropertyInfo() is a method of the EntityStructureWrapper class, which includes wrapped entities and complex fields; calling it against EntityValueWrapper instances (e.g., simple fields/properties like title or an unfiltered Text fields) results in a PHP fatal error.
  • getBundle(): Determine the entity's bundle (e.g., for nodes, the content type).
  • info(): Provides detailed information about the item (entity or field). E.g., I use this when I need a field's label during error handling.
  • validate(): Attempting to set an invalid value via EMW throws a PHP exception, so validate() is a very important in error handling! E.g., the following will output "Bad field value":
    // $value is NULL, title is required.
    if ($wrapper->title->validate($value)) {
      $wrapper->title = $value;
    }
    else {
      echo t("Bad field value");
    }
    

 

 

Back to top

Closing thoughts

With custom entity handling, it's good to test your assumptions as you go, especially when checking for empty values. Proper entity handling can be tricky but is worth it! I use the Devel module's /devel/php page to execute code as I go; there's also Drush's "php-eval" (for one-liners) and "php-script" (for more complex scripts).

And finally, please refer to the d.o documentation for additional examples and clarity. While I don't always agree with its sample code, the documentation provides some useful information that is not covered in detail here. A few important notes about the d.o documentation:

  • Some of the code examples are not PHP 5.3 compatible. E.g., In the date handling section: $wrap_node->field_my_data->value()['value'];
  • For cleaner code, disregard its examples using set() and getIterator().
  • Exception handling: While I recommend following the documentation in this regard, note that you may only catch thrown Exceptions and that PHP fatal errors (such as calling $wrapper->field_foo->value() on an empty wrapper) will still halt your script.

I hope these examples have proven useful to you. Happy wrapping!

 

 

Back to top

Other posts by Derek

comments powered by Disqus