Creating your own entities with Entity API

Feb
03

There have been plenty of articles about Drupal 7 and entities already. Entities are great. They provide a unified way to work with different data units in Drupal. Drupal 7 is all about entities. They are everywhere: nodes, users, taxonomy terms, vocabularies...

But how, as developers, can we create our own entities? When do we really need to do that? I think these questions are really very project-specific. We can probably use nodes for nearly everything. But when it comes to performance-sensitive projects, nodes should really only be used for content, and we should separate as much as possible from nodes. Why? Nodes are revisioned, they fire a lot of hooks, and they have functionality that we likely won't need. Also if we know exactly what fields we should have in our entities, we can create our own custom entities to avoid all those joins of Field API tables.

There are plenty of examples in Drupal core of how to create entities. To create entities, you should really write a lot of code. But there is also, of course, a faster way. You can use Entity API to simplify the code structure. With Entity API we can really move a lot of code to Entity Controller using standard Entity API functions and, in this way, keep our main module clean and nice.

Entity API provides:

  • More functions to work with entities. Drupal core provides functions to work with entities: entity_load(), entity_label(), entity_extract_ids(), entity_uri(), entity_get_info(). Core doesn't have any functions to save or delete entities, but this is where Entity API helps us. It provides: entity_id(), entity_export(), entity_import(), entity_save(), entity_create(), entity_delete(), entity_view(), entity_access(), entity_get_property_info(). This allows us to move more logic for all operations to the Controller Class (we will talk about this bit later).
  • Administration UI for exportable entities. This is a really great feature that allows us to create UIs for exportable entities easily. This is needed when we have bundles as entities. The administrative interface is very similar to the content types overview page for nodes.
  • Metadata wrappers. This allows us nicely get/set values of properties and fields of entities. Also we can get/set data of related entities... like geting the value of a profile field for the author of the node. No more LANGUAGE_NONE everywhere in the code!
  • Integration with Views and Rules. Integration with Rules is done by default when we use the Entity API Controller class, so we can hook in during creating/editing/deleting entities. By default Views can create a view using properties of our Entities, but they are very generic. After we describe the properties to Entity API (this is also needed for wrappers), Views will understand much more. Then we will be able to build different kinds of relationships, exposed filters, etc.
  • Provide entity tokens (entity_token module).

Administration UI screenshot

Every entity has its own Controller. This class is responsible for all operations on this particular entity (create, edit, delete, view, etc). This is very convenient as it can be easily reused by our custom entities, thanks to inheritance. It's also worth mentioning that with Entity API, our entities are "first class" objects and not objects of stdClass.

Lets take a look at a practical implementation of an entity to sample the functionality.

Practical example

Our case is quite abstract: we create two entities, example_task and example_task_type. Task has the properties: uid (author of the task), type, created, changed, title, and description. Task type is simply information about bundles of the task, so it has the fields: id, type, label, and description. Other fields of the Task type are added by Entity API to make this entity exportable (module and status properties).

First of all, (as in case with using only core functionality) in order to implement custom entities we should describe their base tables in hook_schema(). I will avoid listing of all that code here.

Then we should implement hook_entity_info() to declare our entities:

<?php
/**
* Implements hook_entity_info().
*/
function example_task_entity_info() {
 
$return = array(
   
'example_task' => array(
     
'label' => t('Task'),
     
'entity class' => 'ExampleTask',
     
'controller class' => 'ExampleTaskController',
     
'base table' => 'example_task',
     
'fieldable' => TRUE,
     
'entity keys' => array(
       
'id' => 'tkid',
       
'bundle' => 'type',
      ),
     
'bundle keys' => array(
       
'bundle' => 'type',
      ),
     
'bundles' => array(),
     
'load hook' => 'example_task_load',
     
'view modes' => array(
       
'full' => array(
         
'label' => t('Default'),
         
'custom settings' => FALSE,
        ),
      ),
     
'label callback' => 'entity_class_label',
     
'uri callback' => 'entity_class_uri',
     
'module' => 'example_task',
     
'access callback' => 'example_task_access',
    ),
  );
 
$return['example_task_type'] = array(
   
'label' => t('Task Type'),
   
'entity class' => 'ExampleTaskType',
   
'controller class' => 'ExampleTaskTypeController',
   
'base table' => 'example_task_type',
   
'fieldable' => FALSE,
   
'bundle of' => 'example_task',
   
'exportable' => TRUE,
   
'entity keys' => array(
     
'id' => 'id',
     
'name' => 'type',
     
'label' => 'label',
    ),
   
'module' => 'example_task',
   
// Enable the entity API's admin UI.
   
'admin ui' => array(
     
'path' => 'admin/structure/task-types',
     
'file' => 'example_task.admin.inc',
     
'controller class' => 'ExampleTaskTypeUIController',
    ),
   
'access callback' => 'example_task_type_access',
  );

  return $return;
}
?>

I won't describe every option in hook_entity_info() here as there is a lot of documentation and articles about it already (see the list of references in the bottom of this article).

What is specific to Entity API:

  • we define 'entity class'. It is self explanatory.
  • 'label callback' => 'entity_class_label'. This is a standard Entity API label callback that takes a look at our entity class method defaultLabel()
  • 'uri callback' => 'entity_class_uri'. This is also a standard Entity API callback for uri. It executes our entity defaultUri() method
  • 'exportable' key for example_task_type. In our example we set task types to be exportable.
  • 'admin ui' for example_task_type. This is where we define our Administration UI for task types.

The next step in our implementation is to let Drupal understand that entities of example_task_type are bundles for example_task. This should be done in following way:

<?php
/**
* Implements hook_entity_info_alter().
*/
function example_task_entity_info_alter(&$entity_info) {
  foreach (
example_task_types() as $type => $info) {
   
$entity_info['example_task']['bundles'][$type] = array(
     
'label' => $info->label,
     
'admin' => array(
       
'path' => 'admin/structure/task-types/manage/%example_task_type',
       
'real path' => 'admin/structure/task-types/manage/' . $type,
       
'bundle argument' => 4,
      ),
    );
  }
}
?>

Next we should take care about pages where we add, view, and edit our example tasks. To do this we define various items in hook_menu():

<?php
/**
* Implements hook_menu().
*/
function example_task_menu() {
 
$items = array();

  $items['task/add'] = array(
   
'title' => 'Add task',
   
'page callback' => 'example_task_admin_add_page',
   
'access arguments' => array('administer example_task entities'),
   
'file' => 'example_task.admin.inc',
   
'type' => MENU_LOCAL_ACTION,
   
'tab_parent' => 'task',
   
'tab_root' => 'task',
  );

  $task_uri = 'task/%example_task';
 
$task_uri_argument_position = 1;

  $items[$task_uri] = array(
   
'title callback' => 'entity_label',
   
'title arguments' => array('example_task', $task_uri_argument_position),
   
'page callback' => 'example_task_view',
   
'page arguments' => array($task_uri_argument_position),
   
'access callback' => 'entity_access',
   
'access arguments' => array('view', 'example_task', $task_uri_argument_position),
   
'file' => 'example_task.pages.inc',
  );

  $items[$task_uri . '/view'] = array(
   
'title' => 'View',
   
'type' => MENU_DEFAULT_LOCAL_TASK,
   
'weight' => -10,
  );

  $items[$task_uri . '/delete'] = array(
   
'title' => 'Delete task',
   
'title callback' => 'example_task_label',
   
'title arguments' => array($task_uri_argument_position),
   
'page callback' => 'drupal_get_form',
   
'page arguments' => array('example_task_delete_form', $task_uri_argument_position),
   
'access callback' => 'entity_access',
   
'access arguments' => array('edit', 'example_task', $task_uri_argument_position),
   
'file' => 'example_task.admin.inc',
  );

  $items[$task_uri . '/edit'] = array(
   
'title' => 'Edit',
   
'page callback' => 'drupal_get_form',
   
'page arguments' => array('example_task_form', $task_uri_argument_position),
   
'access callback' => 'entity_access',
   
'access arguments' => array('edit', 'example_task', $task_uri_argument_position),
   
'file' => 'example_task.admin.inc',
   
'type' => MENU_LOCAL_TASK,
   
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  );

  foreach (example_task_types() as $type => $info) {
   
$items['task/add/' . $type] = array(
     
'title' => 'Add task',
     
'page callback' => 'example_task_add',
     
'page arguments' => array(2),
     
'access callback' => 'entity_access',
     
'access arguments' => array('create', 'example_task', $type),
     
'file' => 'example_task.admin.inc',
    );
  }

  $items['admin/structure/task-types/%example_task_type/delete'] = array(
   
'title' => 'Delete',
   
'page callback' => 'drupal_get_form',
   
'page arguments' => array('example_task_type_form_delete_confirm', 4),
   
'access arguments' => array('administer example_task types'),
   
'weight' => 1,
   
'type' => MENU_NORMAL_ITEM,
   
'file' => 'example_task.admin.inc',
  );

  return $items;
}
?>

Of note in the hook_menu() implementation is that we use entity_access for most of the access callbacks. The purpose of this function is to check for an access callback defined by the entity ('access callback' property) and execute it. So our access callback for tasks looks like this:

<?php
/**
* Access callback for Task.
*/
function example_task_access($op, $task, $account = NULL, $entity_type = NULL) {
  global
$user;

  if (!isset($account)) {
   
$account = $user;
  }
  switch (
$op) {
    case
'create':
      return
user_access('administer example_task entities', $account)
          ||
user_access('create example_task entities', $account);
    case
'view':
      return
user_access('administer example_task entities', $account)
          ||
user_access('view example_task entities', $account);
    case
'edit':
      return
user_access('administer example_task entities')
          ||
user_access('edit any example_task entities')
          || (
user_access('edit own example_task entities') && ($task->uid == $account->uid));
  }
}
?>

And of course our hook_permissions() for defining our permissions

<?php
/**
* Implements hook_permission().
*/
function example_task_permission() {
 
$permissions = array(
   
'administer example_task types' => array(
     
'title' => t('Administer task types'),
     
'description' => t('Allows users to configure task types and their fields.'),
     
'restrict access' => TRUE,
    ),
   
'create example_task entities' => array(
     
'title' => t('Create tasks'),
     
'description' => t('Allows users to create tasks.'),
     
'restrict access' => TRUE,
    ),
   
'view example_task entities' => array(
     
'title' => t('View tasks'),
     
'description' => t('Allows users to view tasks.'),
     
'restrict access' => TRUE,
    ),
   
'edit any example_task entities' => array(
     
'title' => t('Edit any tasks'),
     
'description' => t('Allows users to edit any tasks.'),
     
'restrict access' => TRUE,
    ),
   
'edit own example_task entities' => array(
     
'title' => t('Edit own tasks'),
     
'description' => t('Allows users to edit own tasks.'),
     
'restrict access' => TRUE,
    ),
  );

  return $permissions;
}
?>

Now let's take a look at the most interesting part – controllers. The Task entity class looks like this:

<?php
/**
* Task class.
*/
class ExampleTask extends Entity {
  protected function
defaultLabel() {
    return
$this->title;
  }

  protected function defaultUri() {
    return array(
'path' => 'task/' . $this->identifier());
  }
}
?>

We only define our label for the entity and what path it should have.

Now Controller class:

<?php
class ExampleTaskController extends EntityAPIController {

  public function create(array $values = array()) {
    global
$user;
   
$values += array(
     
'title' => '',
     
'description' => '',
     
'created' => REQUEST_TIME,
     
'changed' => REQUEST_TIME,
     
'uid' => $user->uid,
    );
    return
parent::create($values);
  }

  public function buildContent($entity, $view_mode = 'full', $langcode = NULL, $content = array()) {
   
$wrapper = entity_metadata_wrapper('example_task', $entity);
   
$content['author'] = array('#markup' => t('Created by: !author', array('!author' => $wrapper->uid->name->value(array('sanitize' => TRUE)))));

    // Make Description and Status themed like default fields.
   
$content['description'] = array(
     
'#theme' => 'field',
     
'#weight' => 0,
     
'#title' =>t('Description'),
     
'#access' => TRUE,
     
'#label_display' => 'above',
     
'#view_mode' => 'full',
     
'#language' => LANGUAGE_NONE,
     
'#field_name' => 'field_fake_description',
     
'#field_type' => 'text',
     
'#entity_type' => 'example_task',
     
'#bundle' => $entity->type,
     
'#items' => array(array('value' => $entity->description)),
     
'#formatter' => 'text_default',
     
0 => array('#markup' => check_plain($entity->description))
    );

    return parent::buildContent($entity, $view_mode, $langcode, $content);
  }
}
?>

Please pay attention to the fact that we inherit our Controller class from EntityAPIController class that is provided by Entity API.

The create() method is responsible for the default values of entity properties when creating a new object. It is imperative that our entity has values when saving the object. In order to create an 'empty' entity we use the entity_create function.

buildContent() is needed for us to have our Description field displayed on the view page for the entity. We show this field using a theming function from the Field API, so the behaviour will be the same as other fields added with fields. Also you can see usage of a wrapper here, but lets come back to it a bit later.

Controller and entity class for example task type look different:

<?php
/**
* Task Type class.
*/
class ExampleTaskType extends Entity {
  public
$type;
  public
$label;
  public
$weight = 0;

  public function __construct($values = array()) {
   
parent::__construct($values, 'example_task_type');
  }

  function isLocked() {
    return isset(
$this->status) && empty($this->is_new) && (($this->status & ENTITY_IN_CODE) || ($this->status & ENTITY_FIXED));
  }
}

class ExampleTaskTypeController extends EntityAPIControllerExportable {
   public function
create(array $values = array()) {
   
$values += array(
     
'label' => '',
     
'description' => '',
    );
    return
parent::create($values);
  }

  /**
   * Save Task Type.
   */
 
public function save($entity, DatabaseTransaction $transaction = NULL) {
   
parent::save($entity, $transaction);
   
// Rebuild menu registry. We do not call menu_rebuild directly, but set
    // variable that indicates rebuild in the end.
    // @see _http://drupal.org/node/1399618
   
variable_set('menu_rebuild_needed', TRUE);
  }
}
?>

Please pay attention to the fact that we inherit our Controller class from EntityAPIControllerExportable class that is provided by Entity API. This is needed to make our entity exportable.

For entity class we define the method isLocked(). This is needed to understand whether this entity is locked so it is not possible to edit/delete it.

In the Controller class we also define the create() method for an empty entity and in the save() method we should call field_attach_create_bundle() to let Fields API know that a new bundle is created. Of course, we also rebuild the menu to refresh our newly created bundle admin paths.

Now we need to define our forms for saving/deleting entities. You can find them in attached module. There is nothing non standard compared to not using Entity API there so let's skip this part (there is plenty of code there as well).

Instead lets take a closer look at usage of other standard Entity API functions in our module.

What we are missing is an API to work with our entities. Here it is:

<?php
/**
* Load a task.
*/
function example_task_load($tkid, $reset = FALSE) {
 
$tasks = example_task_load_multiple(array($tkid), array(), $reset);
  return
reset($tasks);
}

/**
* Load multiple tasks based on certain conditions.
*/
function example_task_load_multiple($tkids = array(), $conditions = array(), $reset = FALSE) {
  return
entity_load('example_task', $tkids, $conditions, $reset);
}

/**
* Save task.
*/
function example_task_save($task) {
 
entity_save('example_task', $task);
}

/**
* Delete single task.
*/
function example_task_delete($task) {
 
entity_delete('example_task', entity_id('example_task' ,$task));
}

/**
* Delete multiple tasks.
*/
function example_task_delete_multiple($task_ids) {
 
entity_delete_multiple('example_task', $task_ids);
}
?>

As you can see, these are simply wrappers around Entity API functions. This is a great benefit of using Entity API as we make our API very simple and transparent.

The same applies for our Task type entity:

<?php
/**
* Load task Type.
*/
function example_task_type_load($task_type) {
  return
example_task_types($task_type);
}

/**
* List of task Types.
*/
function example_task_types($type_name = NULL) {
 
$types = entity_load_multiple_by_name('example_task_type', isset($type_name) ? array($type_name) : FALSE);
  return isset(
$type_name) ? reset($types) : $types;
}

/**
* Save task type entity.
*/
function example_task_type_save($task_type) {
 
entity_save('example_task_type', $task_type);
}

/**
* Delete single case type.
*/
function example_task_type_delete($task_type) {
 
entity_delete('example_task_type', entity_id('example_task_type' ,$task_type));
}

/**
* Delete multiple case types.
*/
function example_task_type_delete_multiple($task_type_ids) {
 
entity_delete_multiple('example_task_type', $task_type_ids);
}
?>

I hope now you start to have a feeling how convenient it is to use standard Entity API functions to build your own entities.

Now let's take a look at metadata wrappers which simplify getting and setting values of entity properties/fields. E.g.

<?php
// Create wrapper around the node.
$wrapper = entity_metadata_wrapper('node', $node);

// We can do it also using only $nid.
$wrapper = entity_metadata_wrapper('node', $nid);

// Get the value of field_name of the nodes author's profile.
$wrapper->author->profile->field_name->value();
$wrapper->author->profile->field_name->set('New name');

// Value of the node's summary in german language.
$wrapper->language('de')->body->summary->value();

// Check whether we can edit node's author email address.
$wrapper->author->mail->access('edit') ? TRUE : FALSE;

// Get roles of node's author.
$wrapper->author->roles->optionsList();

// Set description of the node's first file in field field_files.
$wrapper->field_files[0]->description = 'The first file';
$wrapper->save();

// Get node object.
$node = $wrapper->value();
?>

The entity definition informs meta data wrappers about the raw data structure(base table definition), but this doesn't describe the context of a field's use. For example, the 'uid' field from our task entity is a simple integer and Drupal doesn't know that it is a reference to a user object. In order to create this link we use following code:

<?php
/**
* Implements hook_entity_property_info_alter().
*/
function example_task_entity_property_info_alter(&$info) {
 
$properties = &$info['example_task']['properties'];
 
$properties['created'] = array(
   
'label' => t("Date created"),
   
'type' => 'date',
   
'description' => t("The date the node was posted."),
   
'setter callback' => 'entity_property_verbatim_set',
   
'setter permission' => 'administer nodes',
   
'schema field' => 'created',
  );
 
$properties['changed'] = array(
   
'label' => t("Date changed"),
   
'type' => 'date',
   
'schema field' => 'changed',
   
'description' => t("The date the node was most recently updated."),
  );
 
$properties['uid'] = array(
   
'label' => t("Author"),
   
'type' => 'user',
   
'description' => t("The author of the task."),
   
'setter callback' => 'entity_property_verbatim_set',
   
'setter permission' => 'administer example_task entities',
   
'required' => TRUE,
   
'schema field' => 'uid',
  );
}
?>

We also define 'date' type for our 'created' and 'changed' fields. Now we can use the following way to retrieve the name of our entity author:

<?php
$wrapper
= entity_metadata_wrapper('example_task', $entity);
$wrapper->uid->name->value(array('sanitize' => TRUE));
?>

We can also build views relationship to our entity author. See the screenshot below.


Revisions

At the moment entity revisions are not supported by Entity API. There are steps being made to include this functionality in issue http://drupal.org/node/996696, so your help to move this issue forward is very welcome.

Other examples and documentation

I hope it has become clearer how to simplify creating entities using Entity API. There are a lot of modules that leverage the EntityAPI already like Profile2 and DrupalCommerce.

I would like to thank Wolfgang Ziegler (fago) a lot for this great module and all his work.

References to great resources about Entities and Entity API module:

AttachmentSize
example_task.tar.gz7.79 KB

48 Comments

Great article! Thanks!

Great article! Thanks!

Thanks for the information.

Thanks for the information. Very useful.

yes, many thanks. I already

yes, many thanks. I already studied your great resources links in the past but I also got some new insights by reading this article! Thx!

This is a good article. You

This is a good article. You did a good job of showing the usefulness of entity_metadata_wrapper.

I do warn against putting turning the bundle into an entity itself. It's abstract for the sake of abstract and is unnecessary. Entities are meant to be content and making "types" or bundles entities themselves clouds the namespace.

IMO using ctools export and export_ui plugins is a much better way to build out the bundles. This allows for the same advantages as the entity API, exportability, built in UI, etc.

Still, a good detailed article.

>It's abstract for the sake

>It's abstract for the sake of abstract and is unnecessary. Entities are meant to be content and making "types" or bundles entities themselves clouds the namespace.

No, it's not abstract for the sake of abstract. It's not only great to re-use lots of code for recurring problems (CRUD?), moreover it helps building a consistent developer experience and gives you quite some nice extras. E.g., you'll get the usual CRUD related hooks for your bundle objects as well as views and tokens integration.
Also, entities are not meant to be anything. Nobody planned them. What remains is an API that cares about Drupal's data, use it as you like. But I don't intend to warm-up this old debate so I stop here.

Excellent article. Just what

Excellent article. Just what I needed to get me up and running with the latest Entity API -dev.

One question though ... can you explain why the resulting example_task_type entity is fieldable, despite setting 'fieldable => FALSE' in hook_entity_info?

Jeremy, example_task_type

Jeremy, example_task_type should not be fieldable entity (but example_task is). Why do you think it is fieldable? We cannot add fields to example_task_type entities.

Just my lack of understanding

Just my lack of understanding ... created an instance at admin/structure/task-types, and got the 'manage fields' link - but of course, adding fields here actually applies them to the example_task entities of that given 'task_type', not the type itself. My bad.

Thanks for the excellent

Thanks for the excellent article!

Here some remarks/thoughts from me:

a) You don't need to copy the whole buildContent() function to add some content. For that there is the $content parameter, which you can use to pass in your added $content and call the parent implementation like that.

<?php
public function buildContent($entity, $view_mode = 'full', $langcode = NULL, $content = array()) {
  return
parent::buildContent($entity, $view_mode, $langcode, $your_added_content;
}
?>

b) Your 'permission labels' key in hook_entity_info() is unused. I guess this stems from commerce, which has some nice generic code for generating entity-related permissions and access callbacks. Maybe we should look at pulling this into entity api if feasible.

c) You don't need to call field_attach_create_bundle() yourself. The entity API controller takes care of that for you.

d) Not really related to entities, but you shouldn't call menu_rebuild() directly from API functions as it might lead to lots of subsequent menu rebuilds. Use "variable_set('menu_rebuild_needed', TRUE);" instead or see http://drupal.org/node/1399618 for details.

e) Maybe it would be nice to refer to the documentation templates too, see http://drupal.org/node/999936. Let's make developers aware of all the available APIs and provide API docs in your module's module.api.php file.

Thank you very much for your

Thank you very much for your great feedback.

I have edited module to reflect your suggested changes.

Regarding 'permission labels', yes it is used in commerce_entity_access_permissions to generate set of permissions (http://drupalcode.org/project/commerce.git/blob/refs/heads/7.x-1.x:/comm...). Yes indeed it can be nice idea to pull this code to entity api itself.

Awesome Please tell me the

Awesome

Please tell me the files content modifications for include one foreign constrain between example_task and example_task_type tables.

From structure database point of view the first steps are:
1) delete type field from example_task table.
2) add ref_type_id unsigned integer field in example_task table.

From interface point of view another steps are:
3) delete intermediate selecting task type at creating new task record.
4) add one selecting list ( or combo list) for specify task type at writing new task.
5) modify in the same way the task edit page.

Thanks.

Great article, thanks! The

Great article, thanks! The screenshots are missing though... :S

Thanks for the heads up.

Thanks for the heads up.

Good work! Thanks for nice

Good work! Thanks for nice article.

Great article, this helped me

Great article, this helped me a lot.

Just one question, I would like 2 of my entity types to be connected to eachother, much like comments is connected to nodes, how would you go about that?

Thanks.

I think you should do same

I think you should do same way as comments -- have separate field as id of the entity to be connected with. Maybe you can also use http://drupal.org/project/entityreference field to have this link.

I actually meant actually

I actually meant actually linking the entity types (not instances) so that a one entity type would be automatically created when the linked was is created.

Example:

I have a checklist item entity type and this checklist has a status entity type linked to it.
When i create e new checklist item type I would like for the status entity type to be automatically created together with the checklist item type.
I thought this would be more complicated but this was actually a pretty easy fix in hook_entity_info_alter

For this case I would simply

For this case I would simply use hook_entity_create() to create status entity when checklist entity is created.

Jeremy, example_task_type

Jeremy, example_task_type should not be fieldable entity (but example_task is). Why do you think it is fieldable? We cannot add fields to example_task_type entities.

geertvd repeats my question

geertvd repeats my question with other words...

Thank you very much for

Thank you very much for putting this tutorial together. However, there seem to be many parts missing. You have mentioned that you left out the .install file, but that piece of information is pretty important when trying to figure out how these entities go together. Also you have given a bunch of wrapper functions that have something to do with $node, but no indiciation of where these go or what to do with them.

This article left me a little less confused than I was before reading it, but unfortunately, only a little.

If you down load the module

If you down load the module you will see all the omitted parts. It makes sense not to clutter the article with stuff that are well documented elsewhere.

I agree that the node wrapper examples should be better documented, got me confused for a while.

So as an example could I

So as an example could I create an entity that has fields of other content types, so that upon creating content type A, content type B and C could be created with their own voteable and comment fields?

Sure, but please note that

Sure, but please note that this is possible without entity api (in the case of nodes, you can re-use fields through the manage fields UI).

UUID module

UUID module incompatability.
Great article and code example. Note that this code is incompatable with the UUID module, which disrupts task module's views handlers. Haven't figured out just why yet.

UUID 2 looks like problem

UUID 2
looks like problem with entity api views integration and uuid.
fixed in dev version of uuid as of this date

I seem to have a problem when

I seem to have a problem when trying to install UUID. On my test sites it seems to make the site crash and every page gets a white screen. My host fixes it for me but I'm not sure where to begin fixing the issue that seems to occur with the UUID module. Any ideas?

updated link for the

updated link for the presentation at Drupal Dev Days Brussels: http://bxl2011.drupaldays.org/bxl2011.drupaldays.org/node/313.html

great work, I am using your

great work,

I am using your example module for a project but I need revisions and since the latest dev version of entity API module revisions are included. Perhaps you can make an update in this page to help improving.

Thanks anyway

I can't make my module based

I can't make my module based on yours working with Views. I don't see my Views type in the list when creating the new View. Could you please tell me what exactly part of code made it possible to use Views (to see my type in Views list)?

I've figured what was it, if

I've figured what was it, if you don't make a 'module' property in entity_info you will not see your Views type... :)

Mistake. There is no property

Mistake.
There is no property "access callback" in entity info. It's in "bundles". Regarding your module you can see that access do not work when displaying in Views.

Thank you a lot for your

Thank you a lot for your great work.

Thank you for writing this

Thank you for writing this article.
It is very helpful

I am however having trouble with it.
Once the module is loaded only the Task types menu is available. Where do the menus for the Tasks themselves get called. It seems to be in the module but no menus appear on Drupal.

Pls point me in the right direction.

Thank you.

In addition, when I have tried to use this code on my own entities I receive the following error:

class name must be a valid object or a string in #####/common.inc

The entity type I have declared is of type string, yet I still get the error.

Any ideas?
xK

One permission is missing in

One permission is missing in you example. I you want to be able to access this page :
task/add

You need to add this in hook_permission:

'administer example_task entities' => array(
'title' => t('administer example_task entities'),
'description' => t('Allows users to configure task types and their fields.'),
'restrict access' => TRUE,
),

You are right, Laurent. My

You are right, Laurent. My understanding is that this permission comes probably from the Model project, which does not utilize some permissions thoroughly, such as 'Create', 'Edit any/own'. Therefore I believe that the 'Administer entities' permission is not necessary. The access callback function 'example_task_access' and the function 'example_task_entity_property_info_alter' can be safely altered to disregard this permission (which is not implemented in the first place anyway).

Many thanks for the excellent article, Yurij!

Thanks for all the good info.

Thanks for all the good info. By the way, the page callback function named example_task_view that is referenced in example_task_menu, where does it get defined?

Where can I download the code

Where can I download the code for this module, just too see the missing parts?

I'd love to see this. I would

I'd love to see this. I would really like to understand custom entities, but I really need the information that was left out.

this is realy a great example

this is realy a great example and it helped me a lot in understanding the entity concept.

I have a more complex question on how to build up remote entities "on the fly". I use drupal 7 with a lot of content. there should be a new section, where the content comes from an external source. I get the data as json objects. the data will change, but the structure will be
section 1
task 1
task 2
...
section 2
task a
...

it does not make sence to have doubled data. so I am searching for a solution to build up a structure of remote entities without saving them into the drupal db.

Great article, but there is

Great article, but there is some issue with additional fields.
I created a type and added an image field to it. Next, I uploaded two images to a task. Then, I refreshed my website and there is no image upload possibility now. However, the field is displayed in fields managing list. I can't upload images even to new tasks from this type.

example_task_access expects

example_task_access expects operation 'create', 'view', or 'edit'. The "standard" operations, as documented by entity_access, are 'create', 'view', 'update', and 'delete'. It's probably advisable to stick to those four.

There seems to be some error

There seems to be some error or misunderstanding with one of your code examples:

// Get roles of node's author.
$wrapper->author->roles->optionsList();

This actually doesn't get the entity's "authors" 's roles, but a list of all values applicable to the roles property (i.e. _all_ roles defined in the system, not given to that user!). The correct statement for the purpose stated in the comment would be:

$wrapper->author->roles->value();

Sadly, this then gets only an array of the role's RIDs, and not an associative array in the form of RID=>rolename as we'd get from a simple $user->roles - sometimes using EntityMetadataWrapper makes life more complex instead of simpler... <;)

Hey Thanks a lot for this

Hey
Thanks a lot for this wonderful tutorial and module. Had to struggle for quite sometime to get my entities working. You module integrates well with what I am looking out for, but I tried to get the task to show title instead of id in the url. Somehow the code does not pick that up and throws an error. I even tried changing the table row name to 'name', added the lines below to example_task.module and tried too.

'entity keys' => array(
*******
'name' => 'name',

tried calling this hook from the load hook function too...

$tasks = entity_load_multiple_by_name('example_task',$name);

Can someone guide please?

Thanks for this template!

Thanks for this template! I've already used it for two small entities.

How do you use the hooks within *.api.php? It seems like they are not being called at all.

Thanks for great example This

Thanks for great example
This is a great example and it helped me a lot in understanding the entity concept.

Hrmph.. This was useful,

Hrmph.. This was useful, thanks. You forgot to define ExampleTaskTypeUIController for the admin UI.

This file was included

This file was included 'example_task.admin.inc', but not shown anywhere. Are the UI automatically generated?

I'm curious whether you have to implement everything yourself for the CRUD UI?

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.

More information about formatting options