Drupal Entities - Part 3 - Programming Hello Drupal Entity

After way too long a time I finally get back to the series of posts (check out Part I and Part II) on Entities to present the third in the series - how to actually create an entity. Part of the challenge of writing a post like this is that there are so many different aspects to entities that it is really hard to distill things into a single post that gives you something useful at the end. What we will do will be the equivalent of a "Hello World" for entities. It is not very useful in and of itself but it does get you started and introduces the main concepts.

We are going to create a single entity that has just two fields, an id and some content. For the sake of reference I will call it the PostIt entity. Imagine you wanted to create a simple PostIt system or shoutbox for your Drupal site - this would be part of the way of getting there.

First thing - we need to describe a table to Drupal which will be our "base" table - very much like the Node table, this will be where things start. This description will go into postit.install in a module directory called postit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function postit_schema() {
  $schema['postit'] = array (
    'description' => 'The main store for our entity',
    'fields' => array(
      'pid' => array(
        'description' => 'Primary key for our table of postit notes',
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ),
      'note' => array(
        'description' => 'The actual note',
        'type' => 'varchar',
        'length' => '255',
        'not null' => TRUE
      ),
    ),
    'primary key' => array('pid'),
  );
  
  return $schema;
}

All we are doing here is telling Drupal to create a table with two fields, one of which is the primary key. The table will be created as soon as the module is enabled.

Ok - so we have a starting point. Next up, we need to go tell Drupal that this is the base table of our entity and set up a few things about how it should treat the entity. There is, naturally, a hook for this - aptly called hook_entity_info() and, as with most things Drupal, you construct a huge array with all the configuration information.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function postit_entity_info(){
  $postit_info['postit'] = array(
    'label' => t('PostIt Note'),
    'controller class' => 'PostItController',
    'base table' => 'postit',
    'uri callback' => 'postit_uri',
    'fieldable' => TRUE,
    'entity keys' => array(
      'id' => 'pid',
    ),
    'static cache' => TRUE,
    'bundles' => array(
      'postit'=> array(
        'label' => 'PostIt',
        'admin' => array(
          'path' => 'admin/structure/postit/manage',
          'access arguments' => array('administer postits'),
        ),
      ),
    ),
    'view modes' => array(
      'full' => array(
        'label' => t('Full PostIt'),
        'custom settings' =>  FALSE,
      ),
    )
  );
  
  return $postit_info;
}

Ok - so let us step through this. We provide a label to have a human readable reference to our entity and define a class, the PostItController class, that will be responsible for our postit. This class will actually subclass the DrupalDefaultEntityController class (which lives in entity.inc) and worries about such things such as caching, querying, attaching to fields, etc.

You don't strictly need to define this class (it will automatically use the default one), but if you did want to override some of the existing functionality this would be the way to do it. The class can go in a separate file called postit.controller.inc with just this code for the time being.

1
class PostItController extends DrupalDefaultEntityController{}

We then let Drupal know what the base table will be for our entity and what function should be called to create URIs for this entity. The Entity API will use this function when trying to figure out URIs for entities.

The next one is the biggie, our entity is fieldable which means that the Field API can now hook into our entity and we can easily extend it with fields to our hearts content. We then define what the main identifier for our entity is and we turn on caching.

We now move on to the bundle part, which is really where we practically stitch together the FieldAPI with our entity. A way to think of bundles is as a type of entity (in this case a postit entity) "bundled" together with fields to forms a particular subtype. For example, different types of nodes are different bundles. The Entity API in Drupal can support multiple bundles but in our case we are just defining one and providing a URL where one can go to manage this specific bundle.

Finally, we define view modes - in this case just one.

And that is pretty much it. It could be a bit simpler, but it can also get much more complicated. As I mentioned this is enough for "hello entity!".

Let us now move on to provide the bare minimum functionality to be able to enable our entity module, view an entity and attach fields to it.

First up, taking care of the URI:

1
2
3
4
5
function postit_uri($postit){
  return array(
    'path' => 'postit/' . $postit->id,
  );
}

Then a couple of functions that will load our entities for us:

1
2
3
4
5
6
7
8
9
function postit_load($pid = NULL, $reset = FALSE){
  $pids = (isset ($pid) ? array($pid) : array());
  $postit = postit_load_multiple($pids, $reset);
  return $postit ? reset ($postit) : FALSE;
}
 
function postit_load_multiple($pids = array(), $conditions = array(), $reset = FALSE){
  return entity_load('postit', $pids, $conditions, $reset);
}

The first function simply takes care of whether it is going to load just one or multiple entities and the second function actually goes ahead and loads the entities calling entity_load().

The code below setups the various pages we will use to view our entities. We have a manage page that just provides an access point and link for the Field API to attach itself to our entity via the UI. If you visit "admin/structure/postit/magage" you should see tabs for adding and editing fields.

The other url is where we will view our entity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
function postit_menu(){
  $items['admin/structure/postit/manage'] = array(
    'title' => 'PostIt Admin',
    'description' => 'Manage PostIT structure',
    'page callback' => 'postit_info',
    'access arguments' => array('administer postits'),
  );
  $items['postit/%postit'] = array(
    'title callback' => 'postit_page_title',
    'title arguments' => array(1),
    'page callback' => 'postit_page_view',
    'page arguments' => array(1),
    'access arguments' => array('view postits'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}
 
function postit_permission(){
    return array(
    'administer postits' =>  array(
      'title' => t('Administer postits'),
      'restrict access' => TRUE,
    ),
    'view postsits' => array(
      'title' => t('View PostIts'),
    )
  );
}
 
 
function postit_info() {
  return ('Welcome to the administration page for your Postits!');
}
 
function postit_page_title($postit){
  return $postit->pid;
}
 
function postit_page_view($postit, $view_mode = 'full'){
  $postit->content = array();
 
  // Build fields content.
  field_attach_prepare_view('postit', array($postit->pid => $postit), $view_mode);
  entity_prepare_view('postit', array($postit->pid => $postit));
  $postit->content += field_attach_view('postit', $postit, $view_mode);
 
  return $postit->content;
}
 
 
function postit_field_extra_fields() {
  $return = array();
  $return['postit']['postit'] = array(
    'form' => array(
      'note' => array(
        'label' => t('Note'),
        'description' => t('PostIt Note'),
      ),
    ),
  );
 
  return $return;
}

Now, with all these in place you are able to attach fields to the entity but there is no UI to actually create an entity.

If you visit admin/structure/postit/manage you should be able to see tabs that enable you add and remove fields to your entity.

We will now quickly create a basic UI to add entities so we have just enough to prove everything works together:

We will add another path to hook_menu:

1
2
3
4
5
  $items['postit/add'] = array(
    'title' => 'Add PostIT!',
    'page callback' => 'postit_add',
    'access arguments' => array('create postit'),
  );

and create the function that is called back.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function postit_add() {
  $postit = (object) array (
    'pid' => '',
    'type' => 'postit',
    'note' => '',
  );
  
  return drupal_get_form('postit_add_form', $postit);
}
 
 
function postit_add_form($form, &$form_state, $postit) {
  $form['note'] = array(
    '#type' => 'textfield',
    '#title' => t('Note'),
    '#required' => TRUE,
  );
  
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  
  field_attach_form('postit', $postit, $form, $form_state);
  
  return $form;
}

What we are doing here is creating a form with the note field and then via field_attach_form we are adding any other widgets that are coming via the Field API. The form submission goes through the usual validation steps:

1
2
3
4
5
6
7
8
9
10
11
function postit_add_form_validate($form, &$form_state) {
  $postit_submisttion = (object) $form_state['values'];
  field_attach_form_validate('postit', $postit_submisttion, $form, $form_state);
}
 
function postit_add_form_submit($form, &$form_state) {
  $postit_submission = (object) $form_state['values'];
  field_attach_submit('postit', $postit_submission, $form, $form_state);
  $postit = postit_save($postit_submission);
  $form_state['redirect'] = "postit/$postit->pid";
}

The pattern here is pretty much "Take care of your own fields and then let the Field API do its own thing for the attached fields.

The one function that is missing is the postit_save function which is below:

1
2
3
function postit_save(&$postit) {
  return entity_get_controller('postit')->save($postit);
}

For illustration sake (given that we could do the job without the controller), we delegate the work to our entity controller that has one more function added to it:

1
2
3
4
5
6
  public function save($postit) {
    drupal_write_record('postit', $postit);
    field_attach_insert('postit', $postit);
    module_invoke_all('entity_insert', 'postit', $postit);
    return $postit;
  }

Similar pattern as before, we first save to our own entity table, and then save all the associated fields. Finally, give all the other modules a shout via a hook that an entity has been inserted and we are done.

Visit postit/add you will be able to create the postit and if you've attached any fields to it you will see their widgets there as well. Check the db and you will see your postit table getting updated.

There is still a bit to do, such as deleting and editing entities - offering a page to view a list of all entities, etc - but as I said this is a "Hello Entity!" example. Check out the excellent work that is being done with the Entity project for examples of a full blown CRUD controller for entities.

As you can imagine the possibilities with entities are endless and this has only touched the surface. Hopefully, examples will soon start popping up in the wild and patterns and best practices will start emerging on how to create and handle entities.

Here is a link to a module that I submitted as a patch to the Examples project on d.o. - variable names are different but the approach is the same.

For a more comprehensive and realistic example check out the Model Entities project.

Add Your Comment

Case studies

Roomify — Powerful online booking. Open source comes to travel.
Italy Magazine — Italian lifestyle, travel, and culture.
Integrative Nutrition — 25,000 websites on a single platform
Sandusky Radio — 11 Radio Station Websites in 6 Months, within Budget

Join Now!