Jun 21 2019
Jun 21

I am working with a customer now that is looking to go through a JSON:API upgrade, from version 1.x on Drupal 8.6.x to 2.x and then ultimately to Drupal 8.7.x (where it is bundled into core).

As this upgrade will involve many moving parts, and it is critical to not break any existing integrations (e.g. mobile applications etc), having basic end-to-end tests over the API endpoints is essential.

In the past I have written a lot about CasperJS, and since then a number of more modern frameworks have emerged for end-to-end testing. For the last year or so, I have been involved with Cypress.

I won't go too much in depth about Cypress in this blog post (I will likely post more in the coming months), instead I want to focus specifically on JSON:API testing using Cypress.

In this basic test, I just wanted to hit some known valid endpoints, and ensure the response was roughly OK.

Rather than have to rinse and repeat a lot of boiler plate code for every API end point, I wrote a custom Cypress command, to which abstracts all of this away in a convenient function.

Below is what the spec file looks like (the test definition), it is very clean, and is mostly just the JSON:API paths.

describe('JSON:API tests.', () => {

    it('Agents JSON:API tests.', () => {
        cy.expectValidJsonWithMinimumLength('/jsonapi/node/agent?_format=json&include=field_agent_containers,field_agent_containers.field_cont_storage_conditions&page[limit]=18', 6);
        cy.expectValidJsonWithMinimumLength('/jsonapi/node/agent?_format=json&include=field_agent_containers,field_agent_containers.field_cont_storage_conditions&page[limit]=18&page[offset]=72', 0);
    });
    
    it('Episodes JSON:API tests.', () => {
        cy.expectValidJsonWithMinimumLength('/jsonapi/node/episode?fields[file--file]=uri,url&filter[field_episode_podcast.nid][value]=4976&include=field_episode_podcast,field_episode_audio,field_episode_audio.field_media_audio_file,field_episode_audio.thumbnail,field_image,field_image.image', 6);
    });

});
jsonapi.spec.js

And as for the custom function implementation, it is fairly straight forward. Basic tests are done like:

  • Ensure the response is an HTTP 200
  • Ensure the content-type is valid for JSON:API
  • Ensure there is a response body and it is valid JSON
  • Enforce a minimum number of entities you expect to be returned
  • Check for certain properties in those returned entities.
Cypress.Commands.add('expectValidJsonWithMinimumLength', (url, length) => {
    return cy.request({
        method: 'GET',
        url: url,
        followRedirect: false,
        headers: {
            'accept': 'application/json'
        }
    })
    .then((response) => {
        // Parse JSON the body.
        let body = JSON.parse(response.body);
        expect(response.status).to.eq(200);
        expect(response.headers['content-type']).to.eq('application/vnd.api+json');
        cy.log(body);
        expect(response.body).to.not.be.null;
        expect(body.data).to.have.length.of.at.least(length);

        // Ensure certain properties are present.
        body.data.forEach(function (item) {
            expect(item).to.have.all.keys('type', 'id', 'attributes', 'relationships', 'links');
            ['changed', 'created', 'default_langcode', 'langcode', 'moderation_state', 'nid', 'path', 'promote', 'revision_log', 'revision_timestamp', 'status', 'sticky', 'title', 'uuid', 'vid'].forEach((key) => {
                expect(item['attributes']).to.have.property(key);
            });
        });
    });

});
commands.js

Some of the neat things in this function is that it does log the parsed JSON response with cy.log(body); this allows you to inspect the response in Chrome. This allows you to extend the test function rather easily to meet you own needs (as you can see the full entity properties and fields.

Cypress with a GUI can show you detailed log information

Using Cypress is like having an extra pair of eyes on the Drupal upgrade. Over time Cypress will end up saving us a lot of developer time (and therefore money). The tests will be in place forever, and so regressions can be spotted much sooner (ideally in local development) and therefore fixed much faster.

If you do JSON:API testing with Cypress I would be keen to know if you have any tips and tricks.

Feb 01 2018
Feb 01
February 1st, 2018

Paragraphs is a powerful Drupal module that makes gives editors more flexibility in how they design and layout the content of their pages. However, they are special in that they make no sense without a host entity. If we talk about Paragraphs, it goes without saying that they are to be attached to other entities.
In Drupal 8, individual migrations are built around an entity type. That means we implement a single migration for each entity type. Sometimes we draw relationships between the element being imported and an already imported one of a different type, but we never handle the migration of both simultaneously.
Migrating Paragraphs needs to be done in at least two steps: 1) migrating entities of type Paragraph, and 2) migrating entities referencing imported Paragraph entities.

Migration of Paragraph entities

You can migrate Paragraph entities in a way very similar to the way of migrating every other entity type into Drupal 8. However, a very important caveat is making sure to use the right destination plugin, provided by the Entity Reference Revisions module:

destination: plugin: ‘entity_reference_revisions:paragraph’ default_bundle: paragraph_type destination:plugin:entity_reference_revisions:paragraphdefault_bundle:paragraph_type

This is critical because you can be tempted to use something more common like entity:paragraph which would make sense given that Paragraphs are entities. However, you didn’t configure your Paragraph reference field as a conventional Entity Reference one, but as an Entity reference revisions field, so you need to use an appropriate plugin.

An example of the core of a migration of Paragraph entities:

source: plugin: url data_fetcher_plugin: http data_parser_plugin: json urls: 'feed.url/endpoint' ids: id: type: integer item_selector: '/elements' fields: - name: id label: Id selector: /element_id - name: content label: Content selector: /element_content process: field_paragraph_type_content/value: content destination: plugin: 'entity_reference_revisions:paragraph' default_bundle: paragraph_type migration_dependencies: { } plugin:urldata_fetcher_plugin:httpdata_parser_plugin:jsonurls:'feed.url/endpoint'    type:integeritem_selector:'/elements'    name:id    label:Id    selector:/element_id    name:content    label:Content    selector:/element_contentfield_paragraph_type_content/value:contentdestination:plugin:'entity_reference_revisions:paragraph'default_bundle:paragraph_typemigration_dependencies:{  }

To give some context, this assumes the feed being consumed has a root level with an elements array filled with content arrays with properties like element_id and element_content, and we want to convert those content arrays into Paragraphs of type paragraph_type in Drupal, with the field_paragraph_type_content field storing the text that came from the element_content property.

Migration of the host entity type

Having imported the Paragraph entities already, we then need to import the host entities, attaching the appropriate Paragraphs to each one’s field_paragraph_type_content field. Typically this is accomplished by using the migration_lookup process plugin (formerly migration).

Every time an entity is imported, a row is created in the mapping table for that migration, with both the ID the entity has in the external source and the internal one it got after being imported. This way the migration keeps a correlation between both states of the data, for updating and other purposes.

The migration_lookup plugin takes an ID from an external source and tries to find an internal entity whose ID is linked to the external one in the mapping table, returning its ID in that case. After that, the entity reference field will be populated with that ID, effectively establishing a link between the entities in the Drupal side.

In the example below, the migration_lookup returns entity IDs and creates references to other Drupal entities through the field_event_schools field:

field_event_schools: plugin: iterator source: event_school process: target_id: plugin: migration_lookup migration: schools source: school_id field_event_schools:  plugin:iterator  source:event_school  process:    target_id:      plugin:migration_lookup      migration:schools      source:school_id

However, while references to nodes or terms basically consist of the ID of the referenced entity, when using the entity_reference_revisions destination plugin (as we did to import the Paragraph entities), two IDs are stored per entity. One is the entity ID and the other is the entity revision ID. That means the return of the migration_lookup processor is not an integer, but an array of them.

process: field_paragraph_type_content: plugin: iterator source: elements process: temporary_ids: plugin: migration_lookup migration: paragraphs_migration source: element_id target_id: plugin: extract source: '@temporary_ids' index: - 0 target_revision_id: plugin: extract source: '@temporary_ids' index: - 1 field_paragraph_type_content:  plugin:iterator  source:elements  process:    temporary_ids:      plugin:migration_lookup      migration:paragraphs_migration      source:element_id    target_id:      plugin:extract      source:'@temporary_ids'      index:        -0    target_revision_id:      plugin:extract      source:'@temporary_ids'      index:        -1

What we do then is, instead of just returning an array (it wouldn’t work obviously), use the extract process plugin with it to get the integer IDs needed to create an effective reference.

Summary

In summary, it’s important to remember that migrating Paragraphs is a two-step process at minimum. First, you must migrate entities of type Paragraph. Then you must migrate entities referencing those imported Paragraph entities.

More on Drupal 8

Top 5 Reasons to Migrate Your Site to Drupal 8

Creating your Emulsify 2.0 Starter Kit with Drush

Web Chef Joel Travieso
Joel Travieso

Joel focuses on the backend and architecture of web projects seeking to constantly improve by considering the latest developments of the art.

Web Chef Dev Experts
Development

Blog posts about backend engineering, frontend code work, programming tricks and tips, systems architecture, apps, APIs, microservices, and the technical side of Four Kitchens.

Read more Development
Mar 07 2016
Mar 07
teaser image for blog post

In learning about custom Drupal 8 module development, I found plenty of very simple field module examples, but none that covered how to store more than one value in a field and still have it work properly, so it's time to fix that.

To save you typing or copy and pasting things around all the code in this post is available on Github at https://github.com/ixis/dicefield

Concepts

There are three main elements to define when creating a field type:

  • The field base is the definition of the field itself and contains things like what properties it should have.
  • The field widget defines the form field that is used to put data into your field, what its rules are and how those data are manipulated and stored in the field.
  • The field formatter is how the field will be displayed to the end user and what options are configurable to customise that display.

So far, so familiar if you've ever worked with Drupal 7 fields, and this is like so much of Drupal 8: on the surface, to the end user, it's very similar, but behind the scenes, it's a whole new world.

Use case

To create a (probably quite limited-use, in all honesty) real-world example, I decided to take on the challenge of creating a field to represent dice notation. For example, if you see 1d6 you would grab a single six-sided die and roll it. If you see 3d6-2, you would roll 3 six-sided dice and subtract 2 from the result.

There are three components here:

  • The number of dice
  • The number of sides on each die
  • The modifier: the part that is added or subtracted at the end

Although in practice you could store the whole thing as one big string, and it would be a walk in the park to set up, you would lose some of the more useful functionality, such as search indexing and sorting at a database level. Suppose you wanted to create a view that filtered only field values that involved rolling 5 dice. With a multi-value field such as the one we're creating, it's simple to do. If you store everything as one big string, it involves pattern matching or loading all the results and sifting through them.

Info file

Info files are now YAML format, and this is covered in detail elsewhere, but here's what I came up with:

dicefield.info.yml

name: Dice field
type: module
description: A way of specifying a dice value such as 1d6 or 2d8+3.
package: Field types
version: 1.0
core: 8.x
 
dependencies:
  - field

This is nice and straightforward, and, obviously, our module must depend on the core field module, or it cannot work at all.

Note that Drupal 8 no longer requires anything more than an info file to enable a module; previous versions required an empty .module file at least.

Field base

Now things get interesting. We're going to create a new plugin class to define our field type. The system generally works by extending one of the existing types and making the necessary changes to it. This is the biggest piece of advice I can give beyond reading articles like these: do as little work as possible! Copy/paste from existing things in core or contributed modules and change them to suit (although obviously give credit where it's due).

It's worth noting that, unlike in Drupal 7, our dicefield.info.yml file does not contain a list of "includes" that Drupal 8 should know about. These are loaded automatically by the PSR-4 autoloader, which is both more efficient and more convenient than the previous method. It does mean, however, that you must be careful to lay out your folder structure carefully and make sure things are named properly, because these things do matter in Drupal 8.

src/Plugin/Field/FieldType/Dice.php

/**
 * @file
 * Contains \Drupal\dicefield\Plugin\Field\FieldType\Dice.
 */
 
namespace Drupal\dicefield\Plugin\Field\FieldType;
 
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
 
/**
 * Plugin implementation of the 'dice' field type.
 *
 * @FieldType (
 *   id = "dice",
 *   label = @Translation("Dice"),
 *   description = @Translation("Stores a dice roll such as 1d6 or 2d8+3."),
 *   default_widget = "dice",
 *   default_formatter = "dice"
 * )
 */
class Dice extends FieldItemBase {
  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    return array(
      'columns' => array(
        'number' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => FALSE,
        ),
        'sides' => array(
          'type' => 'int',
          'unsigned' => TRUE,
          'not null' => TRUE,
        ),
        'modifier' => array(
          'type' => 'int',
          'not null' => TRUE,
          'default' => 0,
        ),
      ),
    );
  }
 
  /**
   * {@inheritdoc}
  */
  public function isEmpty() {
    $value1 = $this->get('number')->getValue();
    $value2 = $this->get('sides')->getValue();
    $value3 = $this->get('modifier')->getValue();
    return empty($value1) && empty($value2) && empty($value3);
  }
 
  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    // Add our properties.
    $properties['number'] = DataDefinition::create('integer')
      ->setLabel(t('Number'))
      ->setDescription(t('The number of dice'));
 
    $properties['sides'] = DataDefinition::create('integer')
      ->setLabel(t('Sides'))
      ->setDescription(t('The number of sides on each die'));
 
    $properties['modifier'] = DataDefinition::create('integer')
      ->setLabel(t('Modifier'))
      ->setDescription(t('The modifier to be applied after the roll'));
 
    $properties['average'] = DataDefinition::create('float')
      ->setLabel(t('Average'))
      ->setDescription(t('The average roll produced by this dice setup'))
      ->setComputed(TRUE)
      ->setClass('\Drupal\dicefield\AverageRoll');
 
    return $properties;
  }
}

This looks quite complicated and also quite alien from most things in Drupal 7 unless you're used to working with ctools plugins or the migrate system. Let's break it down a little bit.

The namespace

We have defined our namespace at very nearly the top of the file:

namespace Drupal\dicefield\Plugin\Field\FieldType;

The standard way is to use the Drupal namespace, followed by the name of your module (exactly the same as the name of the folder your module lives in), then the other bits. It's this namespace that will tell the PSR-4 autoloader where to find the classes it needs, so make sure it's correct!

Annotation-based plugin definition

There are multiple ways of defining the plugin's core data. The standard Drupal 8 way is to use annotations, which are like code comment blocks, but contain actual code rather than a comment. Other ways include YAML files, for example, but we're going to keep things simple here.

Note that one downside to using annotations to define plugin data is that since they are effectively comments, not all IDEs can interpret them in the same way as code, so you lose the syntax highlighting and code suggestions associated with writing PHP code in a modern IDE (we use PhpStorm internally). While this might look bad, it's actually not a huge deal because:

  • The plugin definition is a tiny part of your overall code base.
  • The code is right there in front of you, instead of in a separate file.
  • There are still other options if you really don't like it.

Here's the code in question:

/**
 * Plugin implementation of the 'dice' field type.
 *
 * @FieldType (
 *   id = "dice",
 *   label = @Translation("Dice"),
 *   description = @Translation("Stores a dice roll such as 1d6 or 2d8+3."),
 *   default_widget = "dice",
 *   default_formatter = "dice"
 * )
 */

Everything starting from the @FieldType is the plugin definition and everything above is just a regular comment, so you can still write a useful description if you like (and in fact, you should).

The @FieldType part tells Drupal 8 that it is a new field type. There are other annotations that can define various things in Drupal, and we'll see a few others later in the article.

There are a number of key/value pairs in the definition, and these work as follows:

  • id is used to give this plugin a machine name. This only needs to be unique for the type of thing being defined here, so you could have a FieldType called "dice" and also a FieldFormatter called "dice" without worrying about the implications of a namespace collision.
  • label uses the @Translation() notation, which is just like using Drupal's t() function, and provides a human-readable name to be used in the admin UI and other places.
  • description also uses @Translation and just lets users know what your field is for.
  • default_widget is the machine name of the widget that will be used, by default, when this field is put in place on an entity. If there are multiple widgets available, users will be able to pick, but this will be the default. Note that this refers to the machine name of the widget, not the class name. Drupal makes this distinction a lot, so you will become used to working with two different types of notation: Drupal internal machine names, and class names. The class name is not needed here. As long as we define a @FieldWidget plugin later, with an id of "dice", we will be good to go.
  • default_formatter works the same way as default_widget, but is used for the formatter (what the user sees on the front end, rather than the way data are put into your field). Note how these both have the same name. Because they're different plugin types (one is a FieldWidget and the other is a FieldFormatter), they can have the same name and Drupal 8 won't get confused.

There are also a number of other keys that you can use here, but these are best detailed by the Drupal documentation on Entity annotation, and we've covered the ones we need.

Extending classes

Nearly every class you write in Drupal 8 will extend another class, or implement an interface, or apply a trait, or perhaps any combination of those. For example:

class Dice extends FieldItemBase {

We are extending from the base field class here and this will give us all of the functionality we need to implement a new field type. All we have to do is override the methods that we want to work in a different way.

The schema

The schema is simply the definition for how the data will be stored (in the database, or whatever storage engine you're using). We need to return an array (apparently we're still stuck in "array inception" mode for some parts of Drupal 8 but thankfully this is now a lot less common) of arrays, that contain arrays that define the columns we want to store. Yeah, that.

/**
 * {@inheritdoc}
 */
 public static function schema(FieldStorageDefinitionInterface $field_definition) {
   return array(
     'columns' => array(
       'number' => array(
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => FALSE,
       ),
       'sides' => array(
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
       ),
       'modifier' => array(
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
    ),
  );
}

We are basically only defining the columns key in the outer array. In practice it's probably a good idea to define indexes and possibly also foreign keys if your fields will be linked to other data, but let's keep things simple for now.

The definitions in columns work the same way as the Drupal 8 schema API which you can use for reference if you need to.

Notice we're defining three integer fields: one for the number of dice, one for the number of sides on each die, and one for the modifier.

The number of dice and the sides are mandatory, so they do not have a default value. However, you can safely assume that unless otherwise stated, the modifier is optional, and should default to zero, which is why this one has a default value.

Note also that the first two are unsigned, because you can't have a die with -6 sides. The modifier is not unsigned, because both +3 and -3 are valid for modifiers.

The final thing worth mentioning here is that we're only defining fields that are actually stored as data. Later on, we'll see how to derive a computed field, but since the field is a calculated value (which is then cached in the render cache, so stop sweating about performance already!) it is not stored in the database and shouldn't be defined here.

isEmpty

It's very important to tell Drupal how to know if your field is empty or not. Without this, certain basic field functionality will not work properly. In our case, it's quite straightforward: the field is only really "empty" if none of the three values contain anything.

/**
 * {@inheritdoc}
 */
public function isEmpty() {
  $value1 = $this->get('number')->getValue();
  $value2 = $this->get('sides')->getValue();
  $value3 = $this->get('modifier')->getValue();
  return empty($value1) && empty($value2) && empty($value3);
}

Notice how we're using the internal method $this->get() to grab the value? The properties attached to the field will be called the same thing as those in propertyDefinitions() (see below). It makes sense for them to also match the properties we have defined in schema() above, but this does not necessarily have to be the case. Just have a good reason for doing otherwise!

propertyDefinitions

Next, we define the properties that this field will have. These will be the individual pieces of data we can retrieve from the field, and will affect things like view sorting order and how we will set up our formatter later.

/**
 * {@inheritdoc}
 */
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
  // Add our properties.
  $properties['number'] = DataDefinition::create('integer')
    ->setLabel(t('Number'))
    ->setDescription(t('The number of dice'));
 
  $properties['sides'] = DataDefinition::create('integer')
    ->setLabel(t('Sides'))
    ->setDescription(t('The number of sides on each die'));
 
  $properties['modifier'] = DataDefinition::create('integer')
    ->setLabel(t('Modifier'))
    ->setDescription(t('The modifier to be applied after the roll'));
 
  $properties['average'] = DataDefinition::create('float')
    ->setLabel(t('Average'))
    ->setDescription(t('The average roll produced by this dice setup'))
    ->setComputed(TRUE)
    ->setClass('\Drupal\dicefield\AverageRoll');
 
  return $properties;
}

Note that we have the same three properties that we defined as being stored in schema() above, plus a fourth one, called average. This is a computed field, which means that instead of storing the value in the database, we derive it from the values of the other fields. It is more useful to do it this way, because the average value is just the sum of the minimum and maximum possible roll, halved, then added to the modifier. If we were to store this in the database we would be wasting database space. You might think it inefficient to compute this value, but in fact, it's cached by Drupal's render cache system, and invalidated only when the field is updated, so except for the first time it's computed, it's not generally a performance hindrance.

Each of our four properties are basic types as defined by Drupal's typed data API. We have three integers and a float, but we could also use string or other types if we wanted to. We could even come up with our own types, but that's not necessary for this field so I won't cover it here.

We just return an array of properties by using DataDefiniton::create() and chaining the methods we want in order to create the property. As a minimum, you should use setLabel() and setDescription, but there are plenty of others that you can use. The fourth property, average, has two extra methods.

setComputed() is used to indicate that this field is computed rather than stored in the database, so there won't be a matching column in schema().

Given that it's computed, Drupal needs to know what class to use to do this computation, and this is where setClass() comes in. See below for more about computing field values in their own classes.

Widget

Now that we've set up our field base, we need to set up a widget so that people editing a node (or other entity) where this field is used are able to input or edit the data.

src/Plugin/Field/FieldWidget/DiceWidget.php

/**
 * @file
 * Contains \Drupal\dicefield\Plugin\Field\FieldWidget\DiceWidget.
 */
 
namespace Drupal\dicefield\Plugin\Field\FieldWidget;
 
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
 
/**
 * Plugin implementation of the 'dice' widget.
 *
 * @FieldWidget (
 *   id = "dice",
 *   label = @Translation("Dice widget"),
 *   field_types = {
 *     "dice"
 *   }
 * )
 */
class DiceWidget extends WidgetBase {
  /**
   * {@inheritdoc}
   */
  public function formElement(
    FieldItemListInterface $items,
    $delta,
    array $element,
    array &$form,
    array &$form_state
  ) {
    $element['number'] = array(
      '#type' => 'number',
      '#title' => t('# of dice'),
      '#default_value' => isset($items[$delta]->number) ? $items[$delta]->number : 1,
      '#size' => 3,
    );
    $element['sides'] = array(
      '#type' => 'number',
      '#title' => t('Sides'),
      '#field_prefix' => 'd',
      '#default_value' => isset($items[$delta]->sides) ? $items[$delta]->sides : 6,
      '#size' => 3,
    );
    $element['modifier'] = array(
      '#type' => 'number',
      '#title' => t('Modifier'),
      '#default_value' => isset($items[$delta]->modifier) ? $items[$delta]->modifier : 0,
      '#size' => 3,
    );
 
    // If cardinality is 1, ensure a label is output for the field by wrapping
    // it in a details element.
    if ($this->fieldDefinition->getFieldStorageDefinition()->getCardinality() == 1) {
      $element += array(
        '#type' => 'fieldset',
        '#attributes' => array('class' => array('container-inline')),
      );
    }
 
    return $element;
  }
}

Class

Once again, we are inheriting from the base WidgetBase class because almost all of the work involved in being a widget is done for us, so we only need to lift a finger to tell Drupal what's different from the base.

class DiceWidget extends WidgetBase {

Annotation

Again, we see that the annotation at the top of the class defines basic data on this widget:

/**
 * Plugin implementation of the 'dice' widget.
 *
 * @FieldWidget (
 *   id = "dice",
 *   label = @Translation("Dice widget"),
 *   field_types = {
 *     "dice"
 *   }
 * )
 */

This time, @FieldWidget tells Drupal it's dealing with a widget, and the id and label properties work the same way as for the base field above.

We have an array this time, in the form of field_types, which tells Drupal which types of field are allowed to use this widget. Note that unlike regular PHP arrays in Drupal, you must not put a comma after the last element in these arrays.

This field_types allows us to create new widgets, even for existing field types, in case we want a better or different way of inputting data. For example, a geo-location field that stores map coordinates might have a text widget for inputting the data manually, and a separate map widget that allows the user to click on a map to choose a point.

formElement

In actual fact, we only need to override one method in this class:

  /**
   * {@inheritdoc}
   */
  public function formElement(
    FieldItemListInterface $items,
    $delta,
    array $element,
    array &$form,
    FormStateInterface $form_state,
  ) {
    $element['number'] = array(
      '#type' => 'number',
      '#title' => t('# of dice'),
      '#default_value' => isset($items[$delta]->number) ? $items[$delta]->number : 1,
      '#size' => 3,
    );
    $element['sides'] = array(
      '#type' => 'number',
      '#title' => t('Sides'),
      '#field_prefix' => 'd',
      '#default_value' => isset($items[$delta]->sides) ? $items[$delta]->sides : 6,
      '#size' => 3,
    );
    $element['modifier'] = array(
      '#type' => 'number',
      '#title' => t('Modifier'),
      '#default_value' => isset($items[$delta]->modifier) ? $items[$delta]->modifier : 0,
      '#size' => 3,
    );
 
    // If cardinality is 1, ensure a label is output for the field by wrapping
    // it in a details element.
    if ($this->fieldDefinition->getFieldStorageDefinition()->getCardinality() == 1) {
      $element += array(
        '#type' => 'fieldset',
        '#attributes' => array('class' => array('container-inline')),
      );
    }
 
    return $element;
  }

This method tells Drupal how to render the form for this field. Because we need to know three things (the number of dice, the sides per die, and the modifier), we will provide three fields for this. Note how the form keys for these fields match what we defined in schema() and propertyDefinitions() above.

The #attributes on the fieldset causes the fields to be displayed inline instead of one line after another.

These fields use the number field type, which is basically a text field but with little up and down arrows that can be used to increase or decrease the value. It also provides some basic validation in that you need to put a numerical value in here, not a string, and there's no need to write this validation if it's already done for us.

The #default_value key shows how to extract the value from the current field. In the case where we're editing a field, we want the existing values to be in the form ready to be changed, and $items[$delta]->PROPERTY_NAME will do that for us.

I have also set up a default value in the case where we're creating a completely new node, as I felt it was nice to be able to show an example of the required input. Also, since the modifier is often zero, it makes sense to set this as a default value. I could have also used #placeholder to put an HTML5 placeholder value in the field instead of real input.

The last part of this method simply adds a fieldset so that if the field cardinality is 1 (only 1 "dice roll" field value can be put in, instead of allowing unlimited, or a higher number of entries), then the label for the field will still show up properly. This can be used as-is for most field widgets.

Formatter

The last required step (and it might not even be required if you can re-purpose a core formatter from Drupal itself) is to set up a new formatter. This will output the information in the field onto the screen so that users can see it.

src/Plugin/Field/FieldFormatter/DiceFormatter.php

/**
 * @file
 * Contains \Drupal\dicefield\Plugin\Field\FieldFormatter\DiceFormatter.
 */
 
namespace Drupal\dicefield\Plugin\Field\FieldFormatter;
 
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
 
/**
 * Plugin implementation of the 'dice' formatter.
 *
 * @FieldFormatter (
 *   id = "dice",
 *   label = @Translation("Dice"),
 *   field_types = {
 *     "dice"
 *   }
 * )
 */
class DiceFormatter extends FormatterBase {
  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode = NULL) {
    $elements = array();
 
    foreach ($items as $delta => $item) {
      if ($item->sides == 1) {
        // If we are using a 1-sided die (occasionally sees use), just write "1"
        // instead of "1d1" which looks silly.
        $markup = $item->number * $item->sides;
      }
      else {
        $markup = $item->number . 'd' . $item->sides;
      }
 
      // Add the modifier if necessary.
      if (!empty($item->modifier)) {
        $sign = $item->modifier > 0 ? '+' : '-';
        $markup .= $sign . $item->modifier;
      }
 
      $elements[$delta] = array(
        '#type' => 'markup',
        '#markup' => $markup,
      );
    }
 
    return $elements;
  }
}

Annotation

The annotation defines the basic formatter data, and works just like the others above.

  /**
   * Plugin implementation of the 'dice' formatter.
   *
   * @FieldFormatter (
   *   id = "dice",
   *   label = @Translation("Dice"),
   *   field_types = {
   *     "dice"
   *   }
   * )
   */

In fact, this is almost identical to the one for DiceWidget that we defined above, except we're now using the @FieldFormatter type.

Class

As we've seen above, it's easiest to just extend the base class, FormatterBase, since this does all the heavy lifting already and we can pick and choose what to override.

class DiceFormatter extends FormatterBase {

viewElements

We are overriding the method that actually produces the markup that will be displayed on the page:

/**
 * {@inheritdoc}
 */
public function viewElements(FieldItemListInterface $items, $langcode = NULL) {
  $elements = array();
 
  foreach ($items as $delta => $item) {
    if ($item->sides == 1) {
      // If we are using a 1-sided die (occasionally sees use), just write "1"
      // instead of "1d1" which looks silly.
      $markup = $item->number * $item->sides;
    }
    else {
      $markup = $item->number . 'd' . $item->sides;
    }
 
    // Add the modifier if necessary.
    if (!empty($item->modifier)) {
      $sign = $item->modifier > 0 ? '+' : '-';
      $markup .= $sign . $item->modifier;
    }
 
    $elements[$delta] = array(
      '#type' => 'markup',
      '#markup' => $markup,
    );
  }
 
  return $elements;
}

The most important thing here is that we loop through the $items because each field could have a cardinality of greater than one, meaning multiple dice rolls can be stored in a single field.

There is a fringe case where technically it's possible (not in the physical world) to have a one-sided die, which will always roll a 1, no matter what, so instead of writing 1d1, we tell the formatter to present it as just 1 for clarity.

Next we add the modifier, but only if it's a non-zero, because 1d6 looks cleaner than 1d6+0. Note that the modifier could be positive or negative, so we need to account for that in the code. Negative numbers, when converted to strings, already have a negative symbol at the front, but positive ones don't, so we add that on.

The last part is quite important, and that is presenting the return value as a series of render arrays, rather than just plain text. Everything should be presented as a render array where possible, because this allows Drupal to delay its rendering until the last possible moment, affording other modules the opportunity to override where necessary.

AverageRoll type

Earlier, we defined a computed field when we set up propertyDefinitions(). This means that we need to tell Drupal how to compute the value of this field, and we will create a separate class for this.

src/AverageRoll.php

/**
 * @file
 * Contains \Drupal\dicefield\AverageRoll.
 */
 
namespace Drupal\dicefield;
 
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\String;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedData;
 
/**
 * A computed property for an average dice roll.
 */
class AverageRoll extends TypedData {
 
  /**
   * Cached processed value.
   *
   * @var string|null
   */
  protected $processed = NULL;
 
  /**
   * Implements \Drupal\Core\TypedData\TypedDataInterface::getValue().
   */
  public function getValue($langcode = NULL) {
    if ($this->processed !== NULL) {
      return $this->processed;
    }
 
    $item = $this->getParent();
 
    // The minimum roll is the same as the number of dice, which will occur if
    // all dice come up as a 1. Then apply the modifier.
    $minimum = $item->number + $item->modifier;
 
    // The maximum roll is the number of sides on each die times the number of
    // dice. Then apply the modifier.
    $maximum = ($item->number * $item->sides) + $item->modifier;
 
    // Add together the minimum and maximum and divide by two. In cases where we
    // get a fraction, take the lower boundary.
    $this->processed = ($minimum + $maximum) / 2;
    return $this->processed;
  }
 
  /**
   * Implements \Drupal\Core\TypedData\TypedDataInterface::setValue().
   */
  public function setValue($value, $notify = TRUE) {
    $this->processed = $value;
 
    // Notify the parent of any changes.
    if ($notify && isset($this->parent)) {
      $this->parent->onChange($this->name);
    }
  }
}

There is no annotation!

Since we're not defining a plugin here, there's no annotation at the top of this class. There's no need.

Class

We are simply extending an existing type, to do the heavy lifting for us, as with previous classes above.

class AverageRoll extends TypedData {

Caching

I mentioned earlier that Drupal's render cache means that we only need to process the value when it changes, rather than every time we see the field, and we can add a mechanism to achieve this. It's via a protected property:

/**
 * Cached processed value.
 *
 * @var string|null
 */
protected $processed = NULL;

Note how even though this is just a property on a class, it is still fully documented like anything else!

getValue

This method will, as the name suggests, get the value of the computed field when Drupal asks for it. Note that method doesn't need to be called directly. Drupal takes care of this internally which means that you can just use $item->average instead of having to write $item->average->getValue() or anything complicated like that.

/**
 * Implements \Drupal\Core\TypedData\TypedDataInterface::getValue().
 */
public function getValue($langcode = NULL) {
  if ($this->processed !== NULL) {
    return $this->processed;
  }
 
  $item = $this->getParent();
 
  // The minimum roll is the same as the number of dice, which will occur if
  // all dice come up as a 1. Then apply the modifier.
  $minimum = $item->number + $item->modifier;
 
  // The maximum roll is the number of sides on each die times the number of
  // dice. Then apply the modifier.
  $maximum = ($item->number * $item->sides) + $item->modifier;
 
  // Add together the minimum and maximum and divide by two. In cases where we
  // get a fraction, take the lower boundary.
  $this->processed = ($minimum + $maximum) / 2;
  return $this->processed;
}

At the beginning of this method is the test to see if we have already assigned a value to $this->processed. If we have, we don't need to compute the value. We only do that part if the value is null, to save on processing power.

The internals of this method first work out the minimum and maximum possible rolls, applying the modifier to each, and then divide their total by two, which gives the average. For many dice rolls this will be a fraction, which is why we have defined this field as a float rather than an integer.

setValue

Lastly, to make sure cache invalidation works correctly, we need to define a method to take care of setting our value:

/**
 * Implements \Drupal\Core\TypedData\TypedDataInterface::setValue().
 */
public function setValue($value, $notify = TRUE) {
  $this->processed = $value;
 
  // Notify the parent of any changes.
  if ($notify && isset($this->parent)) {
    $this->parent->onChange($this->name);
  }
}

Here, we set $this->processed to the new value, but we also notify the class's parent via the onChange() method. Remember that the vast majority of implementation is handled by the base class instead of making us do the work here, so we ought to be happy to pass the buck to Drupal itself where we can!

AverageRollFormatter

Lastly, it's worth bearing in mind that we have a formatter than can display the dice roll itself, and we also have a computed field that can calculate the average roll, but we have no way of actually showing the average roll to the end user. We will create one more formatter class that will take care of this for us. Because this is a separate formatter, it will allow the site administrator to choose, when displaying dice fields (in views, say), whether to show the dice notation, the average value, or even both (by adding the field twice with different formatters).

src/Plugin/Field/FieldFormatter/AverageRollFormatter.php

/**
 * @file
 * Contains \Drupal\dicefield\Plugin\Field\FieldFormatter\AverageRollFormatter.
 */
 
namespace Drupal\dicefield\Plugin\Field\FieldFormatter;
 
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
 
/**
 * Plugin implementation of the 'average_roll' formatter.
 *
 * @FieldFormatter (
 *   id = "average_roll",
 *   label = @Translation("Average roll"),
 *   field_types = {
 *     "dice"
 *   }
 * )
 */
class AverageRollFormatter extends FormatterBase {
  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode = NULL) {
    $elements = array();
 
    foreach ($items as $delta => $item) {
      $elements[$delta] = array(
        '#type' => 'markup',
        '#markup' => $item->average,
      );
    }
 
    return $elements;
  }
}

The details here are much the same as our DiceFormatter from above, but the implementation is even more simple. Since the value of the field is computed for us already, all we need to do is print it out (by putting it into a render array) in viewElements().

One thing to bear in mind here is that we could have done the average calculation directly in the formatter. There's certainly scope for it: we have all the field data and the ability to execute whatever custom code we like. However, this would have been the wrong thing to do because it would have meant that the average can only be displayed on the front end, and not accessible internally to Drupal. By doing it the way we have done it above, we ensure that, for example, views is able to sort this field by average value, or another developer can plug into this module and grab the average value for his or her own use.

Wrapping up

Now that you have all the elements in place, you can enable the module and try it out!

Once enabled, you will see the new "dice roll" option when picking a field type to add to an entity. When added, you can try creating a new entity of this type, and see the widget in action. Then, the formatter will take care of showing the end result on your entity's view page. Don't forget: you have a choice of two formatters, so you can either show the dice notation or the average value (or even both if you use a custom template or views).

If you have any comments, corrections or observations about this tutorial, please feel free to leave them in the comments below. Hopefully the information about the principles and design decisions will be useful!

The full codebase is available on Github.

Jul 05 2014
Jul 05

How to Build a Drupal 8 Module

Even though Drupal 7 core fell short of a proper way of handling its brand new entity system (we currently rely on the great Entity module for that), it did give us EntityFieldQuery. For those of you who don’t know, EntityFieldQuery is a very powerful querying class used to search Drupal entities programatically (nodes, users, etc).

It provides a number of methods that make it easy to query entities based on conditions such as field values or class properties. If you don’t know how it works, feel free to check out this documentation page or this great tutorial on the subject.

In this article I am going to talk about what we have in Drupal 8 for querying entities. There is no more EntityFieldQuery, but there’s an entity.query service that will instantiate a query object for a given entity type (and that implements the \Drupal\Core\Entity\Query\QueryInterface). We can access this service statically through the \Drupal namespace or using dependency injection.

First up, we’ll look at querying node entities and then we’ll see how to load them. The same techniques will work with other content entities as well (users, comments etc), but also with configuration entities, and that’s really cool.

The entity query service

As mentioned, there are two ways we can access the entity.query service that we use for querying entities. Statically, we can do this:

$query = \Drupal::entityQuery('node');

Instead of node, we can specify any other entity type machine name and what we get inside the $query variable is the query object for our entity type. The entityQuery() static method on the \Drupal namespace is a shortcut for doing so using the entity.query service.

Alternatively (and the highly recommended approach) is to use dependency injection.

If you have access to the container, you can load the service from there and then get the right query object:

$entity_query_service = $container->get('entity.query');
$query = $entity_query_service->get('node');

As you can see, we use the get() method on the entity_query service to instantiate a query object for the entity type with the machine name passed as a parameter.

Querying entities

Let’s illustrate a couple of examples of querying for node entities using this object.

A very simple query that returns the published nodes:

$query = \Drupal::entityQuery('node')
    ->condition('status', 1);
    
$nids = $query->execute();

$nids will be an array of entity ids (in our case node ids) keyed by the revision ids (if there is revisioning enabled for the entity type) or the entity ids if not. Let’s see an example in which we add more property conditions as well as field conditions:

$query = \Drupal::entityQuery('node')
    ->condition('status', 1)
    ->condition('changed', REQUEST_TIME, 'condition('title', 'cat', 'CONTAINS')
    ->condition('field_tags.entity.name', 'cats');

$nids = $query->execute();

In this query, we retrieve the node ids of all the published nodes that have been last updated before the current time, that have the word cat inside their title and that have a taxonomy term called cats as a reference in the field_tags.

As you can see, there is no more distinction between propertyCondition and fieldCondition (as there is in D7 with EntityFieldQuery). Additionally, we can include conditions based on referenced entities tacking on the entity.(column) to the entity reference field name.

An important thing to note is that we also have the langcode parameter in the condition() method by which we can specify what translation of the node should be included in the query. For instance, we can retrieve node IDs that contain a specific value inside of a field in one language but another value inside the same field for another language.

For more information on the condition() method you should consult the API documentation.

The next thing we are going to look at is using condition groups (both AND and OR) for more powerful queries:

$query = \Drupal::entityQuery('node')
    ->condition('status', 1)
    ->condition('changed', REQUEST_TIME, 'orConditionGroup()
    ->condition('title', 'cat', 'CONTAINS')
    ->condition('field_tags.entity.name', 'cats');

$nids = $query->condition($group)->execute();

Above, we altered our previous query so as to retrieve nodes that either have the cat string in their title or have a reference to the term called cats in their field_tags field. And we did so by creating an orConditionGroup object that we then pass to the query as a condition. And we can group together multiple conditions within a andConditionGroup as well.

There are many other methods on the QueryInterface that can extend the query (such as for sorting, range, etc). I encourage you to check them out in the documentation and experiment with them. For now, though, let’s take a quick look at what to do with the result set.

Loading entities

As I mentioned above, the execute() method on the query object we’ve been working with returns an array of entity IDs. Supposedly we now have to load those entity objects and work with them. How do we do that?

In Drupal 7 we had the entity_load() function to which we passed an array of IDs and that would return an array of objects. In Drupal 8, this helper function is maintained and you can use it pretty much in the same way, except only for one entity at a time:

$node = entity_load('node', $nids[1]);

And the return value is a node object. To load multiple nodes, you can use the entity_load_multiple() function:

$nodes = entity_load_multiple('node', $nids);

Which then returns an array of entity objects keyed by their ids.

A bonus nugget of information is that both of these functions are wrappers for the storage manager of the entities in question. They basically retrieve the storage manager statically and then call the load() and loadMultiple() methods, respectively, on it:

Statically, you could do similarly:

$node_storage = \Drupal::entityManager()->getStorage('node');

// Load multiple nodes
$node_storage->loadMultiple($ids);
// Load a single node
$node_storage->load($id);

But better yet, you could use dependency injection and retrieve the storage class from the container:

$node_storage = $container->get('entity.manager')->getStorage('node');

And then proceed with the loading. Using dependency injection is usually the recommended way to go when it’s possible, i.e. when working within a class. This makes it easier to test your class and better decouples it from the rest of the application.

Conclusion

In this article we’ve seen how to work with querying and loading entities in Drupal 8. There has been an overhaul of the D7 EntityFieldQuery class that turned into a robust API for querying both content and configuration entities. We’ve looked at querying content entities but the system works just the same with config entities. And that is a bit of a win for the new Drupal 8 entity system.

We’ve also seen how to load entities based on the IDs resulted in these queries and what is actually behind the wrapper functions that perform these operations. Next up, we are going to look at defining our own content entity type in Drupal 8. For a refresher on how we do it in Drupal 7, you can check out these Sitepoint articles on the subject.

Aug 24 2012
Aug 24

For many years now I've written code to programmatically create nodes. I've done it for dozens of clients starting with Drupal 5. It has always looked the same, been very procedural and felt very wrong. Recently I started playing around with turning this chore into object oriented and started writing a module.

Well yet again it turns out there is already a module for that. The entity module. Not only did it do (almost) everything that I was trying to make my module do but it does far far more. In fact, it has totally revolutionized the way I write integration code.

Have you ever done something like this?

<br /> $node = node_load(1);<br /> $node-&gt;field_reference[LANGUAGE_NONE][0]['nid'] = '463';<br /> $node-&gt;field_text[LANGUAGE_NONE][0]['value'] = 'Some Value';<br /> node_save($node);<br />

If you have, don't ever do it again.

Many of you know that the Entity module is great for defining new entity types using the Entity API but it has another really great API within it. That is turning all the core entities into REAL objects, not just pretend objects. By this I mean that it adds a lot of methods to the objects as well as just properties.

The example above would be:

<br /> $node = new EntityDrupalWrapper('node', 1);<br /> $node-&gt;field_reference[0]-&gt;set(463); // Note that this is a multi value field.<br /> $node-&gt;field_text-&gt;set('Some Value'); // Note that this is a single value field.<br /> $node-&gt;save();<br />

Or if you want to get extra fancy, you can do the following:

class Node extends EntityDrupalWrapper {
public function __construct($data = NULL, $info = array()) {
parent::__construct('node', $data, $info);
}

}

$node = new Node(1);<br /> $node-&gt;field_reference[0]-&gt;set(463); // Note that this is a multi value field.<br /> $node-&gt;field_text-&gt;set('Some Value'); // Note that this is a single value field.<br /> $node-&gt;save();<br />

It makes me so excited to be able to interact with core entities in an entirely object oriented fashion. The Entity API module with the EntityDrupalWrapper will wrap most of the core drupal entities with plenty of useful helper methods.

It has revolutionized the way that I write integration code and hopefully it will yours as well. So next time you need to programmatically create nodes, check out the Entity API and the EntityDrupalWrapper.

Jun 13 2012
Jun 13

In this presentation Karen walks us through the basic concepts of the Organic Groups 7.2 module, also known as OG. She looks at what OG can do for us, explains how it works in Drupal 7 with entities, and does a brief comparison of OG with a multi-site installation.

Jun 07 2012
Jun 07

Posted Jun 7, 2012 // 0 comments


Entities

With the introduction of Drupal 7, the entities architecture was introduced. It allowed users to move away from the concept of “everything is a node”. An entity is a generic object that has a standard API that allows you to do things like attaching fields, track revisions, and write custom displays. Essentially they are an abstraction of nodes. If a developer needs to create custom data tables, they can expose their custom tables as an entity and still retain core functionality.

Moving from Node to Entity

My problem began when I had to find a way to create instances of some of these entities from JSON data received from a series of web service calls. I already knew how to create nodes programmatically, but what about entities? I did a quick Google search and didn’t see much. So I thought that since nodes are entities and I already knew how to manipulate and create new nodes, I figured that I could create new entity instances in a similar manner to nodes. Perhaps changing some settings on the class would work.
Normally when creating a new node instance, I would do:

  $node = new stdClass();
  $node->type = 'candidate';
  $node->bundle = 'node';
  $node->language = LANGUAGE_NONE;
  node_object_prepare($node);

So I tried to rework that by assigning the name of my entity to the type and removing bundle (our entities did not have bundles). This did not seem to work either. Back to Google I went, looking for some more information.

The Entity Creation Problem

Again I came up with very little. Searching for “Drupal 7 create entity” and a dozen other varieties pulled up very little info on how to create actual instances of an entity. Most blog posts revolved around creating an entity type, altering an entity type or searching for an entity.

A few of the resources I came across supported the idea of creating entities by using something like:

entity_get_controller('entity_type_name')->save($entity);

This wasn’t very clear to me. I knew what to use in place of the entity_type_name, but where did the $entity come from? The references seemed to skip over this. Or they assumed you were trying to save an entity from a form entry. Sure, we already had forms for creating the entities, but trying to hook into them didn’t feel like a proper way of creating a new entity. It felt cumbersome and it smelled bad.

I tried to work with this method, but I was never able to get it to work.

Just write directly to the database!

Along came some other ideas on how to create new instances of entities. There were some suggestions that the way to create new entities is by writing directly to the database. Okay, that would work, but I’d really rather keep it to the “Drupal Way” and not resort to hacks. There are pitfalls abound with this solution. What happens if the storage engine changes? Our custom entity types already had custom controllers and their save and load functions. Chances are, these wouldn’t be reflected in this solution.

A New Solution: Entity API Module

At this point, I was getting really frustrated, having spent hours on finding a solution to handling this. Discussing this issue with our Director of Engineering Chris Johnson, I was shown a very simple and straight forward method of programmatically handling entity creation.

      $entity_property_value_array = array();
      $entity_property_value_array['first_name'] = "Brian";
      $entity_property_value_array['last_name'] = "Nash";
      $entity = entity_create($entity_type_name, $entity_property_value_array);
      $entity->save();

And that was it! The entity_create function comes from the Entity API module. It provides functionality that greatly increases the ability to access and set entity data. When assigning a value to a property in a node, one would do something like:

  $node->title = "Brian";

Assigning properties to both a node and an entity are straightforward. The Entity API module also provides meta data functionality that handles the language as well, so assigning field values is less verbose as seen below.

  $node->field_foo['value'][0] = "Bar";

Once I had this figured out, I could process my JSON data by creating a new array that was a entity_property => value array. Granted, that part wasn’t so simple and required handling mapping the names of the fields as they appeared in the JSON object to what they should be in the entity as well as parsing taxonomy lists. But this solution worked and it felt much more straight forward and Drupal-ish than the other methods I saw.

The Entity

API

module is a huge help when creating entities.


Here or some resources I ended up reading up on once I found Entity

API

module. These links provide a good introduction to harnessing entities.


Entity API module
Entity API module tutorial page
Fagos DrupalCon talk on Entity API

As a web developer for Phase2 Brian Nash works to create an effective and structured solution to provide a great experience for the client.

Before joining us, Brian worked for Ruby on Rails that specialized in a large-scale healthcare ...

Jan 16 2012
Jan 16

Back in the olden days of Drupal 5 and 6, the venerable CCK module came with a useful add-on: Content Copy. While it had rough edges, Content Copy allowed you to export a given content type and all of its custom fields into a snippet of PHP. The same module could be used to import that snippet of PHP code on another site and re-create the content type, with all of its settings and custom fields.

Sadly, that feature didn't make the cut when the CCK module became Drupal 7's built-in FieldAPI. While the Features modules has eclipsed Content Copy for serious site deployment, sometimes it would be handy to have the same easy "Export, save, and reuse later" option in Drupal 7. Enter Bundle Copy, the successor to Content Copy.

Screenshot of the Bundle Copy export screen

Like is ancestor, Bundle Copy is pretty straightforward: visit Content Types administration page and click the Export tab, then choose the content types you want to export. Pick the individual fields, submit the form, and voila -- a pile of PHP that can be saved to a text file, and re-imported to another site running Bundle Copy. The module also supports the new entity types that ship with Drupal 7: Users and Taxonomy Terms. If you've heavily customized user profiles with Drupal 7's FieldAPI, it's a quick and easy way to share the tweaks.

Screenshot of the Bundle Copy import screen

Although the Features module is a great solution for full-fledged deployment of Drupal configuration, it carries its own list of module dependencies. If you're simply trying to share some quick tweaks to a content type with a friend, or you'd like to move a content type from one of your sites to another without the overhead of a dedicated Feature module, Bundle Copy is worth checking out.

*/

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web