Programmatically Creating Fielded Nodes in Drupal 7

The Setup

You’ve just created your new super awesome node type in Drupal 7 using the (now in core!) Content type editor.

Drupal 7's content type editor

Now you want to programmatically generate some Super Awesome Content nodes. Some Googling (or Ducking!) will land you with some snippets that look as follows:

function create_my_node() {
    $node = new stdClass();
    $node->type = 'blog';
    node_object_prepare($node);

    $node->title = "My Awesome Title";
    $node->language = LANGUAGE_NONE;
    node_save($node);
}

The Problem

Since you’ve defined a custom content type, you may think something like the following would work great with your new custom node type to set your custom field values:

function create_my_node() {
    $node = new stdClass();
    $node->type = 'super_awesome_content';
    node_object_prepare($node);
   
    $node->title = "My Awesome Title";
    $node->language = LANGUAGE_NONE;
   
    // Try to set your custom field values
    $node->field_reasons_for_being_awesome = "Too many to count...";
    $node->field_awesome_points = 23;
    node_save($node);
}

However, that will fail! Why? Because you can’t set node custom fields like you may expect with $node->{$key}.

In my opinion, this is one thing Drupal 7 got very wrong. I’ll explain their rationale in a bit after showing the correct way to do this and it does make some sense, but it seems unnecessarily complicated.

The Solution

Instead of trying to set those custom field attributes directly, you have to use the following funky syntax:

function create_my_node() {
    $node = new stdClass();
    $node->type = 'super_awesome_content';
    node_object_prepare($node);
  
    $node->title = "My Awesome Title";
    $node->language = LANGUAGE_NONE;
  
    // Try to set your custom field
    $node->field_reasons_for_being_awesome['und'][0]['value'] = "Too many to count...";
    $node->field_awesome_points['und'][0]['value'] = 23;
    node_save($node);
}

Huh? What’s with this ['und'][0]['value'] stuff?

Well, it’s a bit complicated. Let’s break it down piece by piece

['und']

This first bit is the language to be applied to the field. 'und'; is the key specifying that the language is undefined. This could also be 'en' or 'de' or 'fr' or any other 2 letter language code if a particular language is specified for this field.

[0]

This is an array index. For any single value fields like the ones we have, this is just [0]. But it’s conceivable to have multiple values in which this would kind of make sense. For example, let’s say our reasons_for_being_awesome field was a multi-value field:

    ...
    $node->field_reasons_for_being_awesome['und'][0]['value'] = "Too many to count...";
    $node->field_reasons_for_being_awesome['und'][1]['value'] = "Reason number 2";
    $node->field_reasons_for_being_awesome['und'][2]['value'] = "Another reason";
    $node->field_reasons_for_being_awesome['und'][3]['value'] = "The most important reason";
    node_save($node);
}

So it makes some sense for the array index to be there, but it’s somewhat confusing for what is arguably the main use case — namely trying to assign a single value to a field.

['value']

This may be the oddest piece. There are a few other keys which could be specified here, among them ['format'] and ['summary'], but when working programmatically with custom fields, it’s rare to be setting anything other than ['value'].

Summary

// Won't work
$node->field_reasons_for_being_awesome = "Too many to count...";
$node->field_awesome_points = 23;
/ Will work
$node->field_reasons_for_being_awesome['und'][0]['value'] = "Too many to count...";
$node->field_awesome_points['und'][0]['value'] = 23;

Commentary

I think Drupal should be much better here and dropped the ball for developers by not allowing the first method above to work. This ['und'][0]['value'] mess may make it easier for the system to deal with the input, in internationalization and multiplicity, but it makes things far more difficult/complicated for developers in the most common use cases. This is an area where it’s painfully clear that the Drupal Field API is not a true ORM like ActiveRecord and in my opinion that’s a missed opportunity.

This is a situation where things were not optimized for the most common use case causing a lot of developer confusion and headache. I agree with the concept behind the “one is a subset of n” approach here with treating multi-value fields as a multi-value field with just a single element internally (the index [0] bit) but I’m a believer that this should be tucked under a layer of abstraction from most developers rather than making the most common use case of a single-value, non-internationalized field so utterly complex and confusing.

Ideally the system would be able to default to allowing things to be set with just the key and not require developers to specify the language(['und']), array index([0]), and value(['value']) key every single time a value is set to a non-internationalized, single field.

Published 8 Aug 2011

Scaling and leading engineering and data teams to solve the world's problems
Victor Quinn on Twitter