Make an entity type exportable

Last updated on
14 October 2016

If your entity could be considered as site configuration, it is usually a good idea to make it exportable.

What does it mean if an entity is exportable?

By making your entity exportable, you make it possible for site builders to take an entity they've created through the UI and export it so it can be used elsewhere. Familiar examples of exportable configuration are image styles and views. In Drupal 7, you can create an image style through the UI and then export it to be used on another site or included in the code of a module.

The Features module automates the process of exporting configuration to code. If you make your entity type exportable via the Entity API, your entities will automatically be recognized by Features and feature authors will be able to select them and add them to features.

Technical details

When a module defines new entities in code, these entities will be automatically incorporated in the results of entity_load(). This works, as the entity API adds defaults provided by modules to the database automatically upon module installation (for more details about that check entity_defaults_rebuild()).

How to make your entity exportable

To make your entity type exportable, specify EntityAPIControllerExportable as the controller class, set the exportable key in hook_entity_info() to TRUE and include the schema fields necessary (module, status column) by copying the schema as defined in entity_exportable_schema_fields() to your hook_schema() implementation:

/**
 * Implements hook_schema().
 */
function entity_test_schema() {
  $schema['entity_test_type'] = array(
    'description' => 'Stores information about all defined entity_test types.',
    'fields' => array(
      'id' => array(
        'type' => 'serial',
        'not null' => TRUE,
        'description' => 'Primary Key: Unique entity_test type ID.',
      ),
      'name' => array(
        'description' => 'The machine-readable name of this entity_test type.',
        'type' => 'varchar',
        'length' => 32,
        'not null' => TRUE,
      ),
      'label' => array(
        'description' => 'The human-readable name of this entity_test type.',
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
      ),
      'status' => array(
        'type' => 'int',
        'not null' => TRUE,
        // Set the default to ENTITY_CUSTOM without using the constant as it is
        // not safe to use it at this point.
        'default' => 0x01,
        'size' => 'tiny',
        'description' => 'The exportable status of the entity.',
      ),
      'module' => array(
        'description' => 'The name of the providing module if the entity has been defined in code.',
        'type' => 'varchar',
        'length' => 255,
        'not null' => FALSE,
      ),
    ),
    'primary key' => array('id'),
    'unique keys' => array(
      'name' => array('name'),
    ),
  );
  return $schema;
}

If you haven't already, you'll also need to add a dependency on Entity API in your module's .info file:

dependencies[] = entity

As in the example, usually one wants to make use of a machine readable name for an exportable entity type. For this you have to specify the 'name' key under 'entity keys' (in this example to the column above, which is named 'name'), e.g.:

/**
 * Implements hook_entity_info().
 */
function entity_test_entity_info() {
  $return['entity_test_type'] = array(
    'label' => t('Test entity type'),
    'entity class' => 'Entity',
    'controller class' => 'EntityAPIControllerExportable',
    'base table' => 'entity_test_type',
    'fieldable' => FALSE,
    'bundle of' => 'entity_test',
    'exportable' => TRUE,
    'entity keys' => array(
      'id' => 'id',
      'name' => 'name',
    ),
  );
  return $return;
}

Then by default hook_default_YOUR_ENTITY_TYPE() will be invoked for retrieving defaults, whereby the defaults may be overridden by users. The entity API keeps track of the exportable status of each entity, for which it sets the value of the 'status' property to one of the following constants:

<?php
/**
 * A bit flag used to let us know if an entity has been customly defined.
 */
define('ENTITY_CUSTOM', 0x01);

/**
 * A bit flag used to let us know if an entity is a 'default' in code.
 */
define('ENTITY_IN_CODE', 0x02);

/**
 * A bit flag used to mark entities as overridden, e.g. they were originally
 * defined in code and are saved now in the database. Same as
 * (ENTITY_CUSTOM | ENTITY_IN_CODE).
 */
define('ENTITY_OVERRIDDEN', 0x03);

/**
 * A bit flag used to mark entities as fixed, thus not changeable for any
 * user.
 */
define('ENTITY_FIXED', 0x04 | 0x02);
?>

You may also want to have a look at theme_entity_status(), which also provides a human readable description of the statuses.

Note that exportable entities having a name key can be identified by their name or numeric id. The usual numeric id of entities is used internally while loading the entity or to refer to the entity in the database. entity_load() accepts both names and numeric ids for the $ids parameter, but always returns entities keyed by id. For your convenience it is a good idea to define a function that returns exportable entities keyed by name like following:

/**
 * Gets an array of all test entity types, keyed by the name.
 *
 * @param $name
 *   If set, the type with the given name is returned.
 */
function entity_test_get_types($name = NULL) {
  $types = entity_load_multiple_by_name('entity_test_type', isset($name) ? array($name) : FALSE);
  return isset($name) ? reset($types) : $types;
}

Example: An exportable entity serving as bundle

Here is an example of hook_entity_info() from the profile2 module:

  $return['profile_type'] = array(
    'label' => t('Profile type'),
    'entity class' => 'ProfileType',
    'controller class' => 'EntityAPIControllerExportable',
    'base table' => 'profile_type',
    'fieldable' => FALSE,
    'bundle of' => 'profile',
    'exportable' => TRUE,
    'entity keys' => array(
      'id' => 'id',
      'name' => 'name',
    ),
  );

  // Add bundle info but bypass entity_load() as it'll cause function recursion and failure.
  $types = db_select('profile_type', 'p')
    ->fields('p')
    ->execute()
    ->fetchAllAssoc('type');

  foreach ($types as $type_name => $type) {
    $return['profile2']['bundles'][$type_name] = array(
      'label' => $type->label,
      // ...
    );
  }

This defines a new entity for the profile types, which serves as bundle for the fieldable entity 'profile'. It's exportable, so it will be identified by a name (while the numeric id is only used internally) and hook_default_profile_type() will be invoked and its results are incorporated automatically when entity_load() is called.
The 'bundle of' key is an optional feature that lets the EntityAPIController controller care about the field API bundle integration. Apart from that one has to provide all the other bundle related keys as required by hook_entity_info(), i.e. 'bundle keys' and 'bundle'. Refer to profile2_entity_info() or entity_test_entity_info() of the testing module for a complete example providing both, a fieldable entity and an exportable bundle entity.

If you make your entity type exportable, you'll likely also wish to take advantage of the Entity API admin UI support.