Custom Entities: Harness Entity References | Innoraft Skip to main content

Search

7 Dec, 2011
5 min read

Use entity reference in your custom entities (without bundles)

Image
Use entity reference in your custom entities (without bundles) - Banner

Entities are a big boon to Drupal, just that the learning curve is a bit longer than CCKs and nodes. I've read a lot about how these entities work but the more I read, the more complex they sound. Here are small snippets of code that I would like to share to allow you to create an entity reference field with your custom entities.

Lets create a scenario we have 2 entities: "employee" and "company". And we want the entity reference field to be used with our "employee" entity to refer the "company". I use Entity API module to create my custom entities. As it is clear by the name of the entities, we do not need any bundles for them. We can use hook_entity_info to define our entities and use the controller class from EntityAPI. Here is how we do that:

<?php
function MY_MODULE_entity_info() {
  $return = array(
    'employee' => array(
      'label' => t('Employee'),
      'plural label' => t('Employees'),
      'description' => t('An entity which stores all the employee\'s information.'),
      'controller class' => 'EntityAPIController',
      'base table' => 'employee',
      'fieldable' => TRUE,
      'entity keys' => array(
        'id' => 'employee_id',
        'label' => 'name',
      ),
      'uri callback' => 'employee_uri',
    ),
    'company' => array(
      'label' => t('Company'),
      'plural label' => t('Companies'),
      'description' => t('An entity which stores all the company\'s information.'),
      'controller class' => 'EntityAPIController',
      'base table' => 'company',
      'fieldable' => TRUE,
      'entity keys' => array(
        'id' => 'company_id',
        'label' => 'name',
      ),
      'uri callback' => 'company_uri',
    ),
  );
  
  return $return;
}
?>

A little explanation to the above code: You define your entities using hook_entity_info. Before defining the entities, the base tables must be present in the database which means in your implementation of hook_schema you need to define the database for your entity (employee and company table in our case). You dont necessarily need to have bundles for your entities which would mean if you dont specify a bundle the bundle's default name is same as that of the entity. You use EntityAPIController as your controller class to be able to use the CRUD functionalities like entity_save, entity_create, entity_delete, etc.

Next, using hook_menu, we define all the urls for employee and company entities. Lets also assume we have a url for employee/company add/edit form. We now need a select list or autocomplete form item on the employee add/edit form. To do that we have to follow 2 simple steps:

  1. Create a field --> An entry needs to go into field_config table
  2. Create an instance of the field --> An entry needs to go into field_config_instance table

To do the above 2, we need to use field_create_field & field_create_instance respectively. We need to create an entityreference field and attach it to our employee entity. Here is the code that can help us do that:

<?php
function MY_MODULE_install() {
  // Create a field for company reference to be used by various entities
  $field = array(
    'field_name' => 'company_reference',
    'type' => 'entityreference',
    'settings' => array(
      'target_type' => 'company',
      'handler_settings' => array('target_bundles' => NULL),
    ),
    'cardinality' => 1,
    'translatable' => FALSE,
  );
  field_create_field($field);
  
  // Attach the company reference field to employee instance
  $instance = array(
    'field_name' => 'company_reference',
    'entity_type' => 'employee',
    'bundle' => 'employee',
    'label' => 'Company',
    'required' => false,
    'widget' => array(
      'type' => 'options_select'
    ),
    'settings' => array(
      'target_type' => 'company',
      'handler_settings' => array('target_bundles' => NULL),
    ),
    'display' => array(
      'default' => array('label' => 'inline', 'type' => 'entityreference_label'),
    ),
  );

  field_create_instance($instance); 
}
?>

A little explanation to the above code: You need to create the fields and instances only once and therefore you execute your code for the same in the hook_install. Note the use of settings when using field_create_field. "target_type" lets you define the type of the entity to which this field can refer to, "target_bundles" lets you filter the company entities based on the bundle. Unless you have a bundle specified for your entity, it is important to specify NULL over here. In case you do have a bundle use the following settings when creating your field:

'handler_settings' => array('target_bundles' => array('MY_BUNDLE_NAME')),

Note the use of bundle when using field_create_instance. If you look into your field_config_instance table, you'll find that the bundle field is a required field. Remember, if we dont have bundles for an entity, the default bundle name is the same as the entity. Next, note the widget implementation, I'm using a select list instead of autocomplete. To use autocomplete, you just need to specify "entityreference_autocomplete" instead of "options_select". The settings and display portions are self explanatory.

So, now what - everyone says that if you make fields in your entity, you are done, everything is there by default, but tell you what it is actually not so :-) There are 2 things that you'll still have to do:

  1. Tell your employee form to show the fields as well
  2. Make sure that after filling the form, the values rightly go into the database

To do the first part, let me give a little background of my form, I have a dedicated page to add employee and that renders a form using drupal_get_form. Add to it, I use the same form to edit an employee too, so I pass the employee entity as an object to the form. Now, I need to change my form to incorporate the fields. Here is the snippet of the form:

<?php
function employee_form($form, &$form_state, $employee = NULL) {
  // Create a hidden form element when editing a employee if it is an employee edit form
  if ($employee) {
    $form['employee_id'] = array(
      '#type' => 'hidden',
      '#value' => $employee->employee_id,
    );
  }
  
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Employee Name'),
    '#size' => 60,
    '#required' => TRUE,
    '#default_value' => ($employee) ? $employee->name : '',
  );
  
  // Provide a delete link after the submit button in case of editing the employee details
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
    '#suffix' => ($employee) ? l('Delete', 'employee/' . arg(1) . '/delete') : '',
  );
  
  field_attach_form('employee', $employee, $form, $form_state);
  
  return $form;
}
?>

Thats it, the forms start showing on my form, YAY :-) Just one thing left, make sure, they get submitted into the database too. In my form submit, I use entity_save to save the entity. All I need to do is that before I pass the entity object to entity_save, I make sure that the values of the fields (in our case, the entity reference) are also a part of the entity. This is how my form submit looks like after doing that:

<?php
function employee_form_submit($form, &$form_state) {
  // Create an entity object from the form submitted values and pass it to entity_save
  $entity = new stdClass();
  if (array_key_exists('employee_id', $form_state['values'])) {
    $entity->employee_id = $form_state['values']['employee_id'];
    $entity->is_new = FALSE;
  }
  $entity->name = $form_state['values']['name'];
  entity_form_submit_build_entity('employee', $entity, $form, $form_state);
  $result = entity_save('employee', $entity);
  // Display add/update messages
  if ($result == SAVED_NEW)
    drupal_set_message(t('You have successfully added a employee.'));
  if ($result == SAVED_UPDATED)
    drupal_set_message(t('You have successfully updated the employee details.'));
}
?>

Note the use of entity_form_submit_build_entity in the above piece of code. Thats pretty much it. (sweat) Hope this is useful, please share your experience (if you do happen to follow all the above :P