[Drupal] How to create custom entity programatically in Drupal 7

| | 10 min read

An entity is a useful abstraction to make grouping together of fields. Here I am giving you a brief view about how the custom entities created, adding extra fields, adding bundles etc all things attached to entity.

Understanding Entity system

In Drupal, entity acts like a ORM(Object Relation Mapper) that maps and binds some data fields in different tables to a single back boned structure. Entities have properties and extra fields. Properties are those keeps the signature data of that entity (such as status, updated, created, user id), extra fields are those which are stored in separate tables,which has instances of that particular entity. Also another term in entity creation are bundles.

Before going deep into the entity , just look the node structure. Node is an entity, where articles, blog are its bundles. If you add more fields in the structure of the article, a new table named field_data_[field_name] would be created. Node table is the base table of the entity 'node' where created, updated, status, uid etc are its properties.

Start building my custom entity

Let us make sample custom entity. I want to create 2 entities Training and booking.

The structure of the entities are as follows:

Training (2 bundles: main and sub task)

Properties:

  • id
  • created
  • updated
  • type
  • title

Fields

  • description
  • mentor

Booking
Properties:

  • id
  • created
  • updated
  • uid
  • training_id

Fields

  • training_place
  • team_size
  • training_order

Lets start creating entity

Put the code in the .module file.


/*
 * Implements hook_entity_info()
 */
function mymodule_entity_info() {
  $services['training_program'] = array(
    'label' => t('Training program Entity'),// label of the entity to be displayed in the views and all
    'entity class' => 'Entity',// default entity class
    'controller class' => 'EntityAPIController',//default controller class, later we can extent this class to make properties of this entity
    'base table' => 'training_program_data',// base table for saving the entity properties
    'fieldable' => TRUE,//set to true, if extra fields are to be attached to the entity
    'entity keys' => array(// we are setting the entity keys and the bundle as the property named 'type' of this entity. 
      'id' => 'training_data_id',
      'label' => 'name',
      'bundle' => 'type',
    ),
    'view modes' => array(
      'full' => array(
        'label' => t('Default'),
        'custom settings' => FALSE,
      ),
    ),
    'bundle keys' => array(
      'bundle' => 'type',
    ),
    'bundles' => array(// define the bundles in the entity
      'main' => array(
          'label' => t('main', array(), array('context' => 'training main bundle'))
        ),
      'subtask' => array(
          'label' => t('subtask', array(), array('context' => 'training subtask bundle')),
        ),
    ),
    'module' => 'your_module',
    'metadata controller class' => 'TrainingMetadataController',// entity controller class extension in which properties can be created.
    'views controller class' => 'EntityDefaultViewsController',
    'load hook' => 'training_load', //custom entity load function, which is described below
  );
  $services['training_booking'] = array(
    'label' => t('Training Booking Entity'),
    'entity class' => 'Entity',
    'controller class' => 'EntityAPIController',
    'base table' => 'training_booking_data',
    'fieldable' => TRUE,
    'entity keys' => array(
      'id' => 'booking_data_id',
      'label' => 'name',
    ),
    'view modes' => array(
      'full' => array(
        'label' => t('Default'),
        'custom settings' => FALSE,
      ),
    ),
    'module' => 'your_module',
    'metadata controller class' => 'BookingMetadataController',
    'views controller class' => 'EntityDefaultViewsController',
    'load hook' => 'booking_load',
  );
  return $services;
}

Then we are writing some support custom functions to load, create our custom entities


/**
 * Loads a Training by ID.
 */
function training_load($training_id) {
  if (empty($training_id)) {
    return FALSE;
  }
  $trainings = training_load_multiple(array($training_id), array());
  return $trainings ? reset($trainings) : FALSE;
}
/**
 * Loads multiple training by ID or based on a set of matching conditions.
 */
function training_load_multiple($training_ids = array(), $conditions = array(), $reset = FALSE) {
  if (empty($training_ids) && empty($conditions)) {
    return array();
  }
  return entity_load('training_program', $training_ids, $conditions, $reset);
}

/**
 * Returns an initialized training object. Used to create an entity of type training_program.
 */
function training_new() {
  return entity_get_controller('training_program')->create();
}

/**
 * Loads a Training Booking by ID.
 */
function booking_load($booking_id) {
  if (empty($booking_id)) {
    return FALSE;
  }
  $bookings = booking_load_multiple(array($booking_id), array());
  return $bookings ? reset($bookings) : FALSE;
}

/**
 * Loads multiple booking by ID or based on a set of matching conditions.
 */
function booking_load_multiple($booking_ids = array(), $conditions = array(), $reset = FALSE) {
  if (empty($booking_ids) && empty($conditions)) {
    return array();
  }
  return entity_load('training_booking', $booking_ids, $conditions, $reset);
}

/**
 * Returns an initialized booking object. Used to create a entity of type training_booking.
 */
function booking_new() {
  return entity_get_controller('training_booking')->create();
}

Creating Base table

So next step is that we have to create a base table for entity. In this base table the entity id along with the properties saved.
So we created the base table in the hook schema. So create this base table in your install file.


  
/**
 * @file
 * Install, update and uninstall functions for the node module.
 *
 * Implements hook_schema().
 */
function mymodule_schema() {
  $schema['training_program_data'] = array(
    'description' => 'The base table for training data.',
    'fields' => array(
      'training_data_id' => array(
        'description' => 'The primary identifier for a training data.',
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ),
      'created' => array(
        'description' => 'The Unix timestamp when the data was created.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'updated' => array(
        'description' => 'The Unix timestamp when the data was most recently saved.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
     'title' => array(
        'description' => 'The title of this data, always treated as non-markup plain text.',
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
      )
    ),
    'primary key' => array('training_data_id'),
  );
  $schema['training_booking_data'] = array(
    'description' => 'The base table for greyocean training booking data.',
    'fields' => array(
      'booking_data_id' => array(
        'description' => 'The primary identifier for a training booking data.',
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ),
      'uid' => array(
        'description' => 'The {users}.uid that owns this data; initially, this is the user that created it.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'created' => array(
        'description' => 'The Unix timestamp when the data was created.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'updated' => array(
        'description' => 'The Unix timestamp when the data was most recently saved.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'status' => array(
        'description' => 'Integer - (0 ­ submitted, 1 ­ ready for analysis, 2 ­ ready for report, 3 ­ finalised).',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 1,
      ),
      'training_data_id' => array(
        'description' => 'Foreign key training data',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 1,
      ),
    ),
    'foreign keys' => array(
      'training_data_id' => array(
        'table' => 'training_program_data',
        'columns' => array('training_data_id' => 'training_data_id'),
      ),
    ),    
    'primary key' => array('booking_data_id'),
  );
  return $schema;
}

Creating properties

Then we have to create the properties for entities and set them to each base table field. Create a .info.inc file and put the property creation code inside this file. Don`t the forget to include the filename inside the .info file.


/**
 * Extend the defaults.
 */
class TrainingMetadataController extends EntityDefaultMetadataController {
  public function entityPropertyInfo() {
    $info = parent::entityPropertyInfo();
    $properties = &$info[$this->type]['properties'];
    $properties['created'] = array(
      'label' => t('training created'),
      'schema field' => 'created',
      'getter callback' => 'entity_property_getter_method',
      'setter callback' => 'entity_property_verbatim_set',
      'required' => TRUE,
      'description' => t('created date of the assessment data.'),
    );
    $properties['updated'] = array(
      'label' => t('training updated'),
      'schema field' => 'updated',
      'getter callback' => 'entity_property_getter_method',
      'setter callback' => 'entity_property_verbatim_set',
      'required' => TRUE,
      'description' => t('updated date of the assessment data.'),
    );
    $properties['title'] = array(
      'label' => t('training title'),
      'schema field' => 'title',
      'getter callback' => 'entity_property_getter_method',
      'setter callback' => 'entity_property_verbatim_set',
      'required' => TRUE,
      'description' => t('title of the rating data.'),
    );        
     $properties['type'] = array(
      'type' => 'text',
      'label' => t('Type'),
      'description' => t('The human readable name of the bundle type.'),
      'setter callback' => 'entity_property_verbatim_set',
      'getter callback' => 'entity_property_getter_method',
      'options list' => 'mymodule_type_options_list',
      'required' => TRUE,
      'schema field' => 'type',
    );    
    return $info;
  }
}

/**
 * Extend the defaults.
 */
class BookingMetadataController extends EntityDefaultMetadataController {
  public function entityPropertyInfo() {
    $info = parent::entityPropertyInfo();
    $properties = &$info[$this->type]['properties'];
    $properties['uid'] = array(
      'label' => t('booking uid'),
      'schema field' => 'uid',
      'getter callback' => 'entity_property_getter_method',
      'setter callback' => 'entity_property_verbatim_set',
      'required' => TRUE,
      'description' => t('uid of the user created the assement.'),
    );   
    $properties['training_program_id'] = array(
      'label' => t('reference training id'),
      'schema field' => 'training_data_id',
      'getter callback' => 'entity_property_getter_method',
      'setter callback' => 'entity_property_verbatim_set',
      'required' => TRUE,
      'description' => t('training reference ID.'),
    );
    $properties['created'] = array(
      'label' => t('booking created'),
      'schema field' => 'created',
      'getter callback' => 'entity_property_getter_method',
      'setter callback' => 'entity_property_verbatim_set',
      'required' => TRUE,
      'description' => t('created date of the assessment data.'),
    );
    $properties['updated'] = array(
      'label' => t('booking updated'),
      'schema field' => 'updated',
      'getter callback' => 'entity_property_getter_method',
      'setter callback' => 'entity_property_verbatim_set',
      'required' => TRUE,
      'description' => t('updated date of the assessment data.'),
    );
    return $info;
  }
}

Add extra fields

Next step is to create extra fields.

Here in training entity, there are 2 bundles 'main' and 'subtask'. So each extra fields in the 'training' entity should have 2 instances each for main and sub-task. Instances are those which creates entity strains along with the bundle to a field. All the field instances created are recorded in the table field_config_instance. Also all the extra fields created are stored in the table field_config.


/**
 * @file
 * Define field and field instances, attach them to entity bundles.
 */
function mymodule_get_training_field_data() {
  $training_fields = array(
    'training_description' => array(
      'field' => array(
        'field_name' => 'training_description',       // field name
        'entity_types' => array('training_program'),  // entity type to which the field is attached
        'cardinality' => 1,                           // number of values to be saved at a time
        'type' => 'text',                             // type of field
        'settings' => array('max_length' => 255),
      ),
      'instance1' => array(
        'field_name' => 'training_description',
        'label' => 'Training Description',
        'entity_type' => 'training_program',
        'bundle' => 'main',
        'description' => 'The description about Training.',
      ),
      'instance2' => array(
        'field_name' => 'training_description',
        'label' => 'Training Description',
        'entity_type' => 'training_program',
        'bundle' => 'subtask',
        'description' => 'The description about Training.',
      ),
    ),
    'mentor' => array(
      'field' => array(
        'field_name' => 'mentor_description',
        'entity_types' => array('training_program'),
        'cardinality' => 1,
        'type' => 'text',
        'settings' => array('max_length' => 255),
      ),
      'instance1' => array(
        'field_name' => 'mentor_description',
        'label' => 'Training Mentor Description',
        'entity_type' => 'training_program',
        'bundle' => 'main',
        'description' => 'The description about Training Mentor.',
      ),
      'instance2' => array(
        'field_name' => 'mentor_description',
        'label' => 'Training Mentor Description',
        'entity_type' => 'training_program',
        'bundle' => 'subtask',
        'description' => 'The description about Training Mentor.',
      ),
    ),    
  );
  return $training_fields;
}

/**
 * Create fields and field instances for training entity type.
 */
function mymodule_create_training_fields() {
  // Define field and field instances.
  $training_fields = mymodule_get_training_field_data();
  foreach ($training_fields as $field_data) {
    $field = $field_data['field'];
    // Create field if not exists.
    if (!field_read_field($field['field_name'], array('include_inactive' => TRUE))) {
      field_create_field($field);
    }
    $instance1 = $field_data['instance1'];
    // Create field instance if not exists.
    if (!field_read_instance($instance1['entity_type'], $instance1['field_name'], $instance1['bundle'], array('include_inactive' => TRUE))) {
      field_create_instance($instance1);
    }
    $instance2 = $field_data['instance2'];
    // Create field instance if not exists.
    if (!field_read_instance($instance2['entity_type'], $instance2['field_name'], $instance2['bundle'], array('include_inactive' => TRUE))) {
      field_create_instance($instance2);
    }
  }
}

function mymodule_get_booking_field_data() {
  $booking_fields = array(
    'booking_order' => array(    //here booking order is an reference field of another entity commerce_order. SO type = 'entityreference'
      'field' => array(
        'field_name' => 'booking_order',
        'entity_types' => array('training_booking'),
        'cardinality' => 1,
        'type' => 'entityreference',
        'settings' => array(
          'target_type' => 'commerce_order',
        )
      ),
      'instance' => array(              
        'field_name' => 'booking_order',
        'label' => 'booking order',
        'entity_type' => 'training_booking',
        'bundle' => 'training_booking',
        'required' => TRUE,
        'description' => 'The order of booking.',
      ),
    ),
    'team_size' => array(
      'field' => array(
        'field_name' => 'team_size',
        'entity_types' => array('training_booking'),
        'cardinality' => 1,
        'type' => 'number_integer',
      ),
      'instance' => array(
        'field_name' => 'team_size',
        'label' => 'Size of team',
        'entity_type' => 'training_booking',
        'bundle' => 'training_booking',
        'description' => 'Size of the team.',
      ),
    ),
    'training_place' => array(
      'field' => array(
        'field_name' => 'training_place',
        'entity_types' => array('training_booking'),
        'cardinality' => 1,
        'type' => 'text',
        'settings' => array('max_length' => 255),
      ),
      'instance' => array(
        'field_name' => 'training_place',
        'label' => 'Training place',
        'entity_type' => 'training_booking',
        'bundle' => 'training_booking',
        'description' => 'Training Place of the bookie.',
      ),
    ),
  );  
  return $booking_fields;
}

/**
 * Create fields and field instances for training entity type.
 */
function mymodule_create_booking_fields() {
  // Define field and field instances.
  $booking_fields = mymodule_get_booking_field_data();
  foreach ($booking_fields as $field_data) {
    $field = $field_data['field'];
    // Create field if not exists.
    if (!field_read_field($field['field_name'], array('include_inactive' => TRUE))) {
      field_create_field($field);
    }
    $instance = $field_data['instance'];
    // Create field instance if not exists.
    if (!field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE))) {
      field_create_instance($instance);
    }
  }
}

We have to call these field creation functions on hook_enable. Put the below code in the .module file.


/**
 * Implements hook_enable().
 */
function mymodule_enable() {
  mymodule_fields_extra();
}

function mymodule_fields_extra() {  
  module_load_include('inc', 'mymodule', 'includes/mymodule.fields');
  mymodule_create_training_fields();
  mymodule_create_booking_fields();
}

Deleting the extra fields while uninstall

We should create code to delete all the extra fields while uninstalling the module. For that call hook_unistall() in the .install file.


/**
 * Implements hook_uninstall().
 */
function mymodule_uninstall() {
  $training_field_instance = field_info_instances('training_program', 'training_program');
  foreach ($training_field_instance as $field_data) {
    field_delete_instance($field_data);    
  }
  $booking_field_instance = field_info_instances('training_booking', 'training_booking');
  foreach ($booking_field_instance as $field_data) {
    field_delete_instance($field_data);    
  }
}

Create/load entity data

To create an entity:

$training = training_new(); //an object for the entity training_program is created
$time = time();
$training->title = 'training_title';
$training->created = $time;
$training->updated = $time;
$training->type = 'main'; // set the bundle

//and now we go for adding the values for extra fields
global $user;
$language = isset($user->language)?$user->language:'und';
$training->training_description[$language][0]['value'] = 'training detail';
$training->mentor[$language][0]['value'] = 'mentor name';
entity_save('training_program', $training);                    //save the entity

$booking = booking_new();                                      //an object for the entity training_booking is created
$time = time();
global $user;
$booking->title = 'training_title';
$booking->created = $time;
$booking->updated = $time;
$booking->uid = $user->uid;
$training = training_load(1); //load training entity of training_id = 1,
$booking->training_id = $training->training_data_id;

//and now we go for adding the values for extra fields

$language = isset($user->language)?$user->language:'und';
$booking->training_place[$language][0]['value'] = 'training place';
$booking->team_size[$language][0]['value'] = 10;

$booking->booking_order[$language][0]['target_id'] = 102; // this is an entity reference field, so value is saved as 'target_id'

entity_save('training_booking', $training);//save the entity

That`s it!!!!!! your custom entity is fully functioning. If you want to know more about this topic, please get in touch with us.