Implements the basic functionality required to create and display an entity.

This example does not use the Entity API module, which is used by many entity implementations and is recommended by many.

An example of use of creating and managing entities using the Entity API module is provided in the Model Entity module.

@todo: Reference the ronald_istos article series @todo: Reference the Drupal module development book @todo: Add a single field

File

entity_example/entity_example.module
View source
<?php

/**
 * @file
 * Implements the basic functionality required to create and display an entity.
 *
 * This example does not use the
 * @link http://drupal.org/project/entity Entity API module @endlink, which is
 * used by many entity implementations and is recommended by many.
 *
 * An example of use of creating and managing entities using the Entity API
 * module is provided in the
 * @link http://drupal.org/project/model Model Entity module @endlink.
 *
 * @todo: Reference the ronald_istos article series
 * @todo: Reference the Drupal module development book
 * @todo: Add a single field
 */

/**
 * @defgroup entity_example Example: Entity
 * @ingroup examples
 * @{
 * Example creating a core Entity API entity.
 *
 * Note that this example does not use or demonstrate the contrib Entity API,
 * which you can find here: http://drupal.org/project/entity
 */

/**
 * Implements hook_entity_info().
 *
 * This is the fundamental description of the entity.
 *
 * It provides a single entity with a single bundle and without revision
 * support.
 */
function entity_example_entity_info() {
  $info['entity_example_basic'] = array(
    // A human readable label to identify our entity.
    'label' => t('Example Basic Entity'),
    // The controller for our Entity, extending the Drupal core controller.
    'controller class' => 'EntityExampleBasicController',
    // The table for this entity defined in hook_schema()
    'base table' => 'entity_example_basic',
    // Returns the uri elements of an entity.
    'uri callback' => 'entity_example_basic_uri',
    // IF fieldable == FALSE, we can't attach fields.
    'fieldable' => TRUE,
    // entity_keys tells the controller what database fields are used for key
    // functions. It is not required if we don't have bundles or revisions.
    // Here we do not support a revision, so that entity key is omitted.
    'entity keys' => array(
      // The 'id' (basic_id here) is the unique id.
      'id' => 'basic_id',
      // Bundle will be determined by the 'bundle_type' field.
      'bundle' => 'bundle_type',
    ),
    'bundle keys' => array(
      'bundle' => 'bundle_type',
    ),
    // FALSE disables caching. Caching functionality is handled by Drupal core.
    'static cache' => TRUE,
    // Bundles are alternative groups of fields or configuration
    // associated with a base entity type.
    'bundles' => array(
      'first_example_bundle' => array(
        'label' => 'First example bundle',
        // 'admin' key is used by the Field UI to provide field and
        // display UI pages.
        'admin' => array(
          'path' => 'admin/structure/entity_example_basic/manage',
          'access arguments' => array(
            'administer entity_example_basic entities',
          ),
        ),
      ),
    ),
    // View modes allow entities to be displayed differently based on context.
    // As a demonstration we'll support "Tweaky", but we could have and support
    // multiple display modes.
    'view modes' => array(
      'tweaky' => array(
        'label' => t('Tweaky'),
        'custom settings' => FALSE,
      ),
    ),
  );
  return $info;
}

/**
 * Fetch a basic object.
 *
 * This function ends up being a shim between the menu system and
 * entity_example_basic_load_multiple().
 *
 * This function gets its name from the menu system's wildcard
 * naming conventions. For example, /path/%wildcard would end
 * up calling wildcard_load(%wildcard value). In our case defining
 * the path: examples/entity_example/basic/%entity_example_basic in
 * hook_menu() tells Drupal to call entity_example_basic_load().
 *
 * @param int $basic_id
 *   Integer specifying the basic entity id.
 * @param bool $reset
 *   A boolean indicating that the internal cache should be reset.
 *
 * @return object
 *   A fully-loaded $basic object or FALSE if it cannot be loaded.
 *
 * @see entity_example_basic_load_multiple()
 * @see entity_example_menu()
 */
function entity_example_basic_load($basic_id = NULL, $reset = FALSE) {
  $basic_ids = isset($basic_id) ? array(
    $basic_id,
  ) : array();
  $basic = entity_example_basic_load_multiple($basic_ids, array(), $reset);
  return $basic ? reset($basic) : FALSE;
}

/**
 * Loads multiple basic entities.
 *
 * We only need to pass this request along to entity_load(), which
 * will in turn call the load() method of our entity controller class.
 */
function entity_example_basic_load_multiple($basic_ids = FALSE, $conditions = array(), $reset = FALSE) {
  return entity_load('entity_example_basic', $basic_ids, $conditions, $reset);
}

/**
 * Implements the uri callback.
 */
function entity_example_basic_uri($basic) {
  return array(
    'path' => 'examples/entity_example/basic/' . $basic->basic_id,
  );
}

/**
 * Implements hook_menu().
 */
function entity_example_menu() {
  $items['examples/entity_example'] = array(
    'title' => 'Entity Example',
    'page callback' => 'entity_example_info_page',
    'access arguments' => array(
      'view any entity_example_basic entity',
    ),
  );

  // This provides a place for Field API to hang its own
  // interface and has to be the same as what was defined
  // in basic_entity_info() above.
  $items['admin/structure/entity_example_basic/manage'] = array(
    'title' => 'Administer entity_example_basic entity type',
    'page callback' => 'entity_example_basic_list_entities',
    'access arguments' => array(
      'administer entity_example_basic entities',
    ),
  );

  // Add example entities.
  $items['admin/structure/entity_example_basic/manage/add'] = array(
    'title' => 'Add an Entity Example Basic Entity',
    'page callback' => 'entity_example_basic_add',
    'access arguments' => array(
      'create entity_example_basic entities',
    ),
    'type' => MENU_LOCAL_ACTION,
  );

  // List of all entity_example_basic entities.
  $items['admin/structure/entity_example_basic/manage/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );

  // The page to view our entities - needs to follow what
  // is defined in basic_uri and will use load_basic to retrieve
  // the necessary entity info.
  $items['examples/entity_example/basic/%entity_example_basic'] = array(
    'title callback' => 'entity_example_basic_title',
    'title arguments' => array(
      3,
    ),
    'page callback' => 'entity_example_basic_view',
    'page arguments' => array(
      3,
    ),
    'access arguments' => array(
      'view any entity_example_basic entity',
    ),
  );

  // 'View' tab for an individual entity page.
  $items['examples/entity_example/basic/%entity_example_basic/view'] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );

  // 'Edit' tab for an individual entity page.
  $items['examples/entity_example/basic/%entity_example_basic/edit'] = array(
    'title' => 'Edit',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'entity_example_basic_form',
      3,
    ),
    'access arguments' => array(
      'edit any entity_example_basic entity',
    ),
    'type' => MENU_LOCAL_TASK,
  );

  // Add example entities.
  $items['examples/entity_example/basic/add'] = array(
    'title' => 'Add an Entity Example Basic Entity',
    'page callback' => 'entity_example_basic_add',
    'access arguments' => array(
      'create entity_example_basic entities',
    ),
  );
  return $items;
}

/**
 * Basic information for the page.
 */
function entity_example_info_page() {
  $content['preface'] = array(
    '#type' => 'item',
    '#markup' => t('The entity example provides a simple example entity.'),
  );
  if (user_access('administer entity_example_basic entities')) {
    $content['preface']['#markup'] = t('You can administer these and add fields and change the view !link.', array(
      '!link' => l(t('here'), 'admin/structure/entity_example_basic/manage'),
    ));
  }
  $content['table'] = entity_example_basic_list_entities();
  return $content;
}

/**
 * Implements hook_permission().
 */
function entity_example_permission() {
  $permissions = array(
    'administer entity_example_basic entities' => array(
      'title' => t('Administer entity_example_basic entities'),
    ),
    'view any entity_example_basic entity' => array(
      'title' => t('View any Entity Example Basic entity'),
    ),
    'edit any entity_example_basic entity' => array(
      'title' => t('Edit any Entity Example Basic entity'),
    ),
    'create entity_example_basic entities' => array(
      'title' => t('Create Entity Example Basic Entities'),
    ),
  );
  return $permissions;
}

/**
 * Returns a render array with all entity_example_basic entities.
 *
 * In this basic example we know that there won't be many entities,
 * so we'll just load them all for display. See pager_example.module
 * to implement a pager. Most implementations would probably do this
 * with the contrib Entity API module, or a view using views module,
 * but we avoid using non-core features in the Examples project.
 *
 * @see pager_example.module
 */
function entity_example_basic_list_entities() {
  $content = array();

  // Load all of our entities.
  $entities = entity_example_basic_load_multiple();
  if (!empty($entities)) {
    foreach ($entities as $entity) {

      // Create tabular rows for our entities.
      $rows[] = array(
        'data' => array(
          'id' => $entity->basic_id,
          'item_description' => l($entity->item_description, 'examples/entity_example/basic/' . $entity->basic_id),
          'bundle' => $entity->bundle_type,
        ),
      );
    }

    // Put our entities into a themed table. See theme_table() for details.
    $content['entity_table'] = array(
      '#theme' => 'table',
      '#rows' => $rows,
      '#header' => array(
        t('ID'),
        t('Item Description'),
        t('Bundle'),
      ),
    );
  }
  else {

    // There were no entities. Tell the user.
    $content[] = array(
      '#type' => 'item',
      '#markup' => t('No entity_example_basic entities currently exist.'),
    );
  }
  return $content;
}

/**
 * Callback for a page title when this entity is displayed.
 */
function entity_example_basic_title($entity) {
  return t('Entity Example Basic (item_description=@item_description)', array(
    '@item_description' => $entity->item_description,
  ));
}

/**
 * Menu callback to display an entity.
 *
 * As we load the entity for display, we're responsible for invoking a number
 * of hooks in their proper order.
 *
 * @see hook_entity_prepare_view()
 * @see hook_entity_view()
 * @see hook_entity_view_alter()
 */
function entity_example_basic_view($entity, $view_mode = 'tweaky') {

  // Our entity type, for convenience.
  $entity_type = 'entity_example_basic';

  // Start setting up the content.
  $entity->content = array(
    '#view_mode' => $view_mode,
  );

  // Build fields content - this is where the Field API really comes in to play.
  // The task has very little code here because it all gets taken care of by
  // field module.
  // field_attach_prepare_view() lets the fields load any data they need
  // before viewing.
  field_attach_prepare_view($entity_type, array(
    $entity->basic_id => $entity,
  ), $view_mode);

  // We call entity_prepare_view() so it can invoke hook_entity_prepare_view()
  // for us.
  entity_prepare_view($entity_type, array(
    $entity->basic_id => $entity,
  ));

  // Now field_attach_view() generates the content for the fields.
  $entity->content += field_attach_view($entity_type, $entity, $view_mode);

  // OK, Field API done, now we can set up some of our own data.
  $entity->content['created'] = array(
    '#type' => 'item',
    '#title' => t('Created date'),
    '#markup' => format_date($entity->created),
  );
  $entity->content['item_description'] = array(
    '#type' => 'item',
    '#title' => t('Item Description'),
    '#markup' => $entity->item_description,
  );

  // Now to invoke some hooks. We need the language code for
  // hook_entity_view(), so let's get that.
  global $language;
  $langcode = $language->language;

  // And now invoke hook_entity_view().
  module_invoke_all('entity_view', $entity, $entity_type, $view_mode, $langcode);

  // Now invoke hook_entity_view_alter().
  drupal_alter(array(
    'entity_example_basic_view',
    'entity_view',
  ), $entity->content, $entity_type);

  // And finally return the content.
  return $entity->content;
}

/**
 * Implements hook_field_extra_fields().
 *
 * This exposes the "extra fields" (usually properties that can be configured
 * as if they were fields) of the entity as pseudo-fields
 * so that they get handled by the Entity and Field core functionality.
 * Node titles get treated in a similar manner.
 */
function entity_example_field_extra_fields() {
  $form_elements['item_description'] = array(
    'label' => t('Item Description'),
    'description' => t('Item Description (an extra form field)'),
    'weight' => -5,
  );
  $display_elements['created'] = array(
    'label' => t('Creation date'),
    'description' => t('Creation date (an extra display field)'),
    'weight' => 0,
  );
  $display_elements['item_description'] = array(
    'label' => t('Item Description'),
    'description' => t('Just like title, but trying to point out that it is a separate property'),
    'weight' => 0,
  );

  // Since we have only one bundle type, we'll just provide the extra_fields
  // for it here.
  $extra_fields['entity_example_basic']['first_example_bundle']['form'] = $form_elements;
  $extra_fields['entity_example_basic']['first_example_bundle']['display'] = $display_elements;
  return $extra_fields;
}

/**
 * Provides a wrapper on the edit form to add a new entity.
 */
function entity_example_basic_add() {

  // Create a basic entity structure to be used and passed to the validation
  // and submission functions.
  $entity = entity_get_controller('entity_example_basic')
    ->create();
  return drupal_get_form('entity_example_basic_form', $entity);
}

/**
 * Form function to create an entity_example_basic entity.
 *
 * The pattern is:
 * - Set up the form for the data that is specific to your
 *   entity: the columns of your base table.
 * - Call on the Field API to pull in the form elements
 *   for fields attached to the entity.
 */
function entity_example_basic_form($form, &$form_state, $entity) {
  $form['item_description'] = array(
    '#type' => 'textfield',
    '#title' => t('Item Description'),
    '#required' => TRUE,
    '#default_value' => $entity->item_description,
  );
  $form['basic_entity'] = array(
    '#type' => 'value',
    '#value' => $entity,
  );
  field_attach_form('entity_example_basic', $entity, $form, $form_state);
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
    '#weight' => 100,
  );
  $form['delete'] = array(
    '#type' => 'submit',
    '#value' => t('Delete'),
    '#submit' => array(
      'entity_example_basic_edit_delete',
    ),
    '#weight' => 200,
  );
  return $form;
}

/**
 * Validation handler for entity_example_basic_add_form form.
 *
 * We pass things straight through to the Field API to handle validation
 * of the attached fields.
 */
function entity_example_basic_form_validate($form, &$form_state) {
  field_attach_form_validate('entity_example_basic', $form_state['values']['basic_entity'], $form, $form_state);
}

/**
 * Form submit handler: Submits basic_add_form information.
 */
function entity_example_basic_form_submit($form, &$form_state) {
  $entity = $form_state['values']['basic_entity'];
  $entity->item_description = $form_state['values']['item_description'];
  field_attach_submit('entity_example_basic', $entity, $form, $form_state);
  $entity = entity_example_basic_save($entity);
  $form_state['redirect'] = 'examples/entity_example/basic/' . $entity->basic_id;
}

/**
 * Form deletion handler.
 *
 * @todo: 'Are you sure?' message.
 */
function entity_example_basic_edit_delete($form, &$form_state) {
  $entity = $form_state['values']['basic_entity'];
  entity_example_basic_delete($entity);
  drupal_set_message(t('The entity %item_description (ID %id) has been deleted', array(
    '%item_description' => $entity->item_description,
    '%id' => $entity->basic_id,
  )));
  $form_state['redirect'] = 'examples/entity_example';
}

/**
 * We save the entity by calling the controller.
 */
function entity_example_basic_save(&$entity) {
  return entity_get_controller('entity_example_basic')
    ->save($entity);
}

/**
 * Use the controller to delete the entity.
 */
function entity_example_basic_delete($entity) {
  entity_get_controller('entity_example_basic')
    ->delete($entity);
}

/**
 * EntityExampleBasicControllerInterface definition.
 *
 * We create an interface here because anyone could come along and
 * use hook_entity_info_alter() to change our controller class.
 * We want to let them know what methods our class needs in order
 * to function with the rest of the module, so here's a handy list.
 *
 * @see hook_entity_info_alter()
 */
interface EntityExampleBasicControllerInterface extends DrupalEntityControllerInterface {

  /**
   * Create an entity.
   */
  public function create();

  /**
   * Save an entity.
   *
   * @param object $entity
   *   The entity to save.
   */
  public function save($entity);

  /**
   * Delete an entity.
   *
   * @param object $entity
   *   The entity to delete.
   */
  public function delete($entity);

}

/**
 * EntityExampleBasicController extends DrupalDefaultEntityController.
 *
 * Our subclass of DrupalDefaultEntityController lets us add a few
 * important create, update, and delete methods.
 */
class EntityExampleBasicController extends DrupalDefaultEntityController implements EntityExampleBasicControllerInterface {

  /**
   * Create and return a new entity_example_basic entity.
   */
  public function create() {
    $entity = new stdClass();
    $entity->type = 'entity_example_basic';
    $entity->basic_id = 0;
    $entity->bundle_type = 'first_example_bundle';
    $entity->item_description = '';
    return $entity;
  }

  /**
   * Saves the custom fields using drupal_write_record().
   */
  public function save($entity) {

    // If our entity has no basic_id, then we need to give it a
    // time of creation.
    if (empty($entity->basic_id)) {
      $entity->created = time();
    }

    // Invoke hook_entity_presave().
    module_invoke_all('entity_presave', $entity, 'entity_example_basic');

    // The 'primary_keys' argument determines whether this will be an insert
    // or an update. So if the entity already has an ID, we'll specify
    // basic_id as the key.
    $primary_keys = $entity->basic_id ? 'basic_id' : array();

    // Write out the entity record.
    drupal_write_record('entity_example_basic', $entity, $primary_keys);

    // We're going to invoke either hook_entity_update() or
    // hook_entity_insert(), depending on whether or not this is a
    // new entity. We'll just store the name of hook_entity_insert()
    // and change it if we need to.
    $invocation = 'entity_insert';

    // Now we need to either insert or update the fields which are
    // attached to this entity. We use the same primary_keys logic
    // to determine whether to update or insert, and which hook we
    // need to invoke.
    if (empty($primary_keys)) {
      field_attach_insert('entity_example_basic', $entity);
    }
    else {
      field_attach_update('entity_example_basic', $entity);
      $invocation = 'entity_update';
    }

    // Invoke either hook_entity_update() or hook_entity_insert().
    module_invoke_all($invocation, $entity, 'entity_example_basic');
    return $entity;
  }

  /**
   * Delete a single entity.
   *
   * Really a convenience function for deleteMultiple().
   */
  public function delete($entity) {
    $this
      ->deleteMultiple(array(
      $entity,
    ));
  }

  /**
   * Delete one or more entity_example_basic entities.
   *
   * Deletion is unfortunately not supported in the base
   * DrupalDefaultEntityController class.
   *
   * @param array $entities
   *   An array of entity IDs or a single numeric ID.
   */
  public function deleteMultiple($entities) {
    $basic_ids = array();
    if (!empty($entities)) {
      $transaction = db_transaction();
      try {
        foreach ($entities as $entity) {

          // Invoke hook_entity_delete().
          module_invoke_all('entity_delete', $entity, 'entity_example_basic');
          field_attach_delete('entity_example_basic', $entity);
          $basic_ids[] = $entity->basic_id;
        }
        db_delete('entity_example_basic')
          ->condition('basic_id', $basic_ids, 'IN')
          ->execute();
      } catch (Exception $e) {
        $transaction
          ->rollback();
        watchdog_exception('entity_example', $e);
        throw $e;
      }
    }
  }

}

/**
 * @} End of "defgroup entity_example".
 */

Functions

Namesort descending Description
entity_example_basic_add Provides a wrapper on the edit form to add a new entity.
entity_example_basic_delete Use the controller to delete the entity.
entity_example_basic_edit_delete Form deletion handler.
entity_example_basic_form Form function to create an entity_example_basic entity.
entity_example_basic_form_submit Form submit handler: Submits basic_add_form information.
entity_example_basic_form_validate Validation handler for entity_example_basic_add_form form.
entity_example_basic_list_entities Returns a render array with all entity_example_basic entities.
entity_example_basic_load Fetch a basic object.
entity_example_basic_load_multiple Loads multiple basic entities.
entity_example_basic_save We save the entity by calling the controller.
entity_example_basic_title Callback for a page title when this entity is displayed.
entity_example_basic_uri Implements the uri callback.
entity_example_basic_view Menu callback to display an entity.
entity_example_entity_info Implements hook_entity_info().
entity_example_field_extra_fields Implements hook_field_extra_fields().
entity_example_info_page Basic information for the page.
entity_example_menu Implements hook_menu().
entity_example_permission Implements hook_permission().

Classes

Namesort descending Description
EntityExampleBasicController EntityExampleBasicController extends DrupalDefaultEntityController.

Interfaces

Namesort descending Description
EntityExampleBasicControllerInterface EntityExampleBasicControllerInterface definition.