Coding Drupal Entities - part 2, hook_menu

If you are implementing hook_menu in your custom module to define paths for handling entity CRUD operations, chances are you're just duplicating code. The Entity API defines all of the menu items you'll be using for the CRUD operations of your custom entities.

Even if you happen to need some custom paths for your entities, you might want to consider implementing a hook_menu method in a custom controller class.

Let's see how Entity API is defining all the menu items you need:

1. hook_menu

The hook_menu implementation in the Entity API module is pretty simple, but very powerful:

function entity_menu() {
  $items = array();
  foreach (entity_ui_controller() as $controller) {
    $items += $controller->hook_menu();
  }
  return $items;
}

entity_ui_controller() gets all UI controllers of your custom entities - these were defined in ['admin ui']['controller class'] in hook_entity_info - and calls the hook_menu method of these controller classes. This method returns the same type of array that you would use in a standard hook_menu implementation.

If you need a custom path, you might want to consider overriding the hook_menu method of the default controller instead of implementing hook_menu directly. Doing this could make your code more reusable.

2. Configuration entities

Let's take a look at the menu path Entity API defines for configuration entities (like node type, profile type, etc.).

The default controller is EntityDefaultUIController and EntityDefaultUIController::hook_menu defines the following menu items:

Overview form: $this->path

This is defined in hook_entity_info in ['admin ui']['path'], it's usually something like 'admin/structure/myentity-types' (e.g.: admin/structure/types for nodes). Entity API is kind enough to build an overview form to add fields to your custom entities, manage displays, etc., the same things you got used to on the Content types admin page.

The menu callback is drupal_get_form, however what happens here is that drupal_retrieve_form invokes hook_forms and entity_forms will call the hook_forms method of the UI controller (EntityDefaultUIController::hook_forms() by default) and finally the EntityDefaultUIController::overviewForm() method will be called.

access callback: entity_access
access arguments:

  • 'view'
  • entity machine name (e.g. node or profile2)

Add new type: $this->path / add

This brings you to a form where you can create new entity types. Pretty much like adding a new node type. What is available here will depend on what properties your entity has.

The page callback function for this path is entity_ui_get_form, which calls the form builder function. This is how the form id is set:

$form_id = (!isset($bundle) || $bundle == $entity_type) ? $entity_type . '_form' : $entity_type . '_edit_' . $bundle . '_form';

Chances are your configuration entity type does not have bundles, the form id will be set to myentity_type_form and that will be the name of the form builder function you'll have to implement in order to show the add/edit form. Submission and validation handlers should also be implemented.

You can put the form builder function into a separate file, you just need to define ['admin ui']['file'] in hook_entity_info().

page callback: entity_ui_get_form
page arguments: array($this->entityType, NULL, 'add'),

access callback: entity_access
access arguments:

  • 'create'
  • entity machine name (e.g. node or profile2)

Edit types: $this->path / % / manage

This is where you can edit the properties of a given entity type. It's like editing a node type, where you can change settings like publishing options, comments settings, etc. Quite similar to the add new type page above, the only difference is that the properties of the entity you are editing are loaded to set the default values of the form.

page callback: entity_ui_get_form
page arguments: array($this->entityType, $this->id_count + 1)

access callback: entity_access
access arguments:

  • 'update'
  • entity machine name (e.g. node or profile2)
  • entity type machine name (e.g. page, article for nodes)

Clone a type: $this->path / % / clone

Pretty self-explanatory, you can clone a type.

page callback: entity_ui_get_form
page arguments: array($this->entityType, $this->id_count + 1, 'clone')

access callback: entity_access
access arguments:

  • 'create'
  • entity machine name (e.g. node or profile2)

Delete, revert, export a type: $this->path / % / %

I've only used this for deleting types so far. It takes you to a confirmation form. It's a bit strange, but the entity_access function is called with $op = 'delete' if you want to export a type. (However you'll usually implement one permission, e.g. 'administer this entity type' for all CRUD operations of configuration entities...)

The callback function is drupal_get_form again, however what happens here is that EntityDefaultUIController::operationForm() will be called.

access callback: entity_access
access arguments:

  • 'delete'
  • entity machine name (e.g. node or profile2)
  • entity type machine name (e.g. page, article for nodes)

Import: $this->path / import

Pretty straightforward again, if your type is exportable, you can import a previous export here.

access callback: entity_access
access arguments:

  • 'create'
  • entity machine name (e.g. node or profile2)

3. Bundleable Content entities

The default controller is EntityBundleableUIController, which extends EntityContentUIController, which extends EntityDefaultUIController. Let's see its hook_menu:

Add new entity: $this->path / add, $this->path / add / %

The $this->path/add page will show links to create the bundles, the $this->path/add/% will take you to the entity creation form. Since the bundle name is not passed to the access callback, you cannot implement per bundle create rights, unless you override the EntityBundleableUIController::hook_menu().

You are responsible for building the form for adding/editing content entities. The form builder function must have the name entitytype_form ( see EntityDefaultUIController::hook_forms() ).

access callback: entity_access
access arguments:

  • 'create'
  • entity machine name (e.g. node or profile2)

View an entity: $this->path / %

This is like the standard node/[nid] page.

access callback: entity_access
access arguments:

  • 'create'
  • entity machine name (e.g. node or profile2)
  • the wildcard from the menu (i.e. the entity id)

Edit an entity: $this->path / % / edit

This is the edit form, you'll need to create it. It's the same form that is used to add a new entity.

access callback: entity_access
access arguments:

  • 'edit'
  • entity machine name (e.g. node or profile2)
  • the wildcard from the menu (i.e. the entity id)

Delete an entity: $this->path / % / delete

Takes you to a confirmation form, which is provided by Entity API.

access callback: entity_access
access arguments:

  • 'delete'
  • entity machine name (e.g. node or profile2)
  • the wildcard from the menu (i.e. the entity id)

Overview

The only thing you need to code is the add/edit form for content and configuration entites. Everything else could/should be done by Entity API. Chances are you won't even need to implement hook_menu in your custom module.

However you'll need to define permissions and access checks in your module, this is an overview of what permissions Entity API uses:

configuration entites: view, create, update, delete

Chances are you'll be defining one "master" permission - e.g. "administer this entity type", just like "administer content types" for nodes.

content entites: view, create, edit, delete

The bundle name is not passed to the access callback when you want to create a new entity, so you cannot have permissions like "create page content" for nodes if you are using the default Entity API controllers.

Services

Drupal theming and sitebuild

  • PSD to Drupal theme
  • Installing and configuring modules (Views, CCK, Rules, etc.)
  • Theming modules' output

Drupal module development

  • Almost every site needs smaller modules to modify HTML output or override certain default functionalities
  • Developing custom modules

From the blog