Pluggable Entity Operations in Drupal 7

In Drupal 7 core all the common objects, like nodes, users, comments, and terms, are called entities. Entities are a base level conceptual object designed to be used as a base for objects in core and contrib. But, there is a problem with the core entity pattern we can improve on in our contrib modules.

Entities have a controller used for their loading operations. The controller is a class that can be swapped out for alternative controllers when developers want to do something fancy, like have their own node caching setup. But, only the load operation is on the controller. The other CRUD (create, read, and delete) are hard coded in functions like node_load, node_save, and other object operations. The CRUD control for entities can’t be completely swapped out. Let’s take a look at a base pattern to use for completely swappable controllers.

Why a completely swappable controller?

There are several reasons to provide a fully swappable controller. There are different use cases that are important to different developers. Let's look at a reason that is becoming fairly common.

Alternative databases are becoming all the rage. MongoDB and Drupal are already on their way to being used together where they can. Some developers will want to store entities in alternative locations like a MongoDB database. To do this all of the CRUD operations need to be swapped out.

The Pattern

Lets look at a code example to see how completely swappable CRUD would work. We start by looking at hook_entity_info where the controller is defined
/**
 * Implements hook_entity_info().
 */
function example_entity_info() {
  $return =  array(
    'example' => array(
      'label' => t('Example Entity'),
      'controller class' => 'ExampleEntityController',
      'base table' => 'example_entity',
      'fieldable' => TRUE,
      'path callback' => 'example_entity_path',
      'object keys' => array(
        'id' => 'id',
      ),
      ...
    ),
  );
  return $return;
}

Here we create a basic entity called example and specify ExampleEntityController as the controller. We cannot specify the default controller provided by core because it only has the load operation. But, we can extend the default controller allowing us to use everything it has and add our own additional methods.

class ExampleEntityController extends DrupalDefaultEntityController {

  public function save($entity) {
    ...
  }

  public function delete($entity_ids) {
    ...
  }

  public function create() {
    ...
  }
}

The pattern in Drupal is to have functions for the CRUD operations. For example, node has node_load, node_save, and node_delete. In keeping form with these we should create example_load, example_load_multiple, example_save, example_delete, and example_create functions that call back to the controller. For the most part these functions should be pretty simple. For example,

function example_save($entity) {
  return entity_get_controller('example')->save($entity);
}

Calling entity_get_controller('example') returns the controller for that entity. In this case we call the save method and pass in the entity object to be saved. The idea is that the crud functions simply pass though the functionality to the controller.

Why not Entity Module?

The Entity Module is attempting to provide a base to do something like this and more. But, it does a lot more than provide a pattern for swappable operations. It introduces Entity objects, the facade pattern, and more. At this point I'm not ready to recommend its approach.

How we go about advancing entities beyond pluggable operations still seems to be under a bit of healthy debate. This post shows the first step in a better direction. There will be more to follow as the healthy discussion turns into solid approaches.