The 'drupal' way to implement delete button UI: part 2 I asked this question earlier The 'drupal' way to implement delete button UI 
The answer worked perfectly. I would like to take it a step further and make sure my routing is done the best way for Drupal. (I'd hate to think there's some uber-powerful method out there I'm missing out on).

snippet from hook_menu():

$items['bluenose/rmlist/%'] = array(
  'page callback' =&gt; 'bluenose_app_rmelement',
  'page arguments' =&gt; array(1, 2),
  'access arguments' =&gt; array ('access bluenose_app wishlists'),
  'type' =&gt; MENU_LOCAL_TASK,
);
$items['bluenose/rmlistitem/%']=array(
  'page callback' =&gt; 'bluenose_app_rmelement',
  'page arguments' =&gt; array(1, 2),
  'access arguments' =&gt; array ('access bluenose_app wishlists'),
  'type' =&gt; MENU_LOCAL_TASK,
);
$items['bluenose/rmavailableprop/%']=array(
  'page callback' =&gt; 'bluenose_app_rmelement',
  'page arguments' =&gt; array(1, 2),
  'access arguments' =&gt; array ('access bluenose_app wishlists'),
  'type' =&gt; MENU_LOCAL_TASK,
);


The method those route to: 

function bluenose_app_rmelement($type, $id)
{
  switch($type)
  {
  case 'rmlist':
    bluenose_app_remove_wishlist($id);
    drupal_goto('bluenose/wishlists');
    break;
  case 'rmlistitem':
    bluenose_app_remove_list_item($id);
    drupal_goto('');
    break;
  case 'rmavailableproperty':
    bluenose_app_remove_available_property($id);
    drupal_goto('');
    break;
  default:
    break;

  }
}


Just to clarify the flow: 
There are three hook_menu items that route $type and $id to a common rm* method. 
This method switches among possible $type params. When it hits the right one, it calls the appropriate remove method (which performs basic crud), then calls drupal_goto() with a url that makes sense to the end-user.

How's that method chain look to you guys?
Thanks a lot!
It does look good! But you could remove the repeating hook_menu items as well.

When I said the following,


  When the entity gets complicated, I often end up writing a single menu
  router to handle them all. The operation argument of a menu path
  (edit, delete, clone, disable, etc) can be a wildcard loader as well,
  and you can easily validate it using a method_exists() call.


I actually meant that you'd use one menu router item that passes extra variables to resolve the call to an actual function. Now that you have posted some real code, I'd go ahead and show an example for you. Note that this is not an official way to do this, but I practice this quite a lot and never had a problem. 

The only menu router item we need.

$items['bluenose/%/%'] = array(
  'page callback' =&gt; 'bluenose_app_rmelement',
  'page arguments' =&gt; array(1, 2),
  'access arguments' =&gt; array ('access bluenose_app wishlists'),
  'type' =&gt; MENU_CALLBACK,
);


The page callback to handle 'em all.

function bluenose_app_rmelement($type, $id) {
  switch($type) {
    case 'rmlist':
      bluenose_app_remove_wishlist($id);
      drupal_goto('bluenose/wishlists');
      break;
    case 'rmlistitem':
      bluenose_app_remove_list_item($id);
      drupal_goto('');
      break;
    case 'rmavailableproperty':
      bluenose_app_remove_available_property($id);
      drupal_goto('');
      break;
    default:
      break;
  }
}


Now, you will notice that the menu router item is actually of type MENU_CALLBACK, and its not of type MENU_LOCAL_TASK. If you want to make it a local task, it has to be defined so in the hook_menu... But you can also do so in a hook_menu_local_tasks_alter() hook implementation. 

If you absolutely need to make all the operation links to be MENU_LOCAL_TASKS, you can either go for the multiple router item way (from your original question's snippet) or implement hook_menu_local_tasks_alter(). With this hook, you are dynamically adding menu router items only when the current router item is what we are after.

Also, note that menu paths that immediately execute the functions are vulnerable to CSRF attacks. When you create the URLs, you should add a token to the link and validate it in the page callback. See drupal_get_token() and drupal_valid_token().