1

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' => 'bluenose_app_rmelement',
  'page arguments' => array(1, 2),
  'access arguments' => array ('access bluenose_app wishlists'),
  'type' => MENU_LOCAL_TASK,
);
$items['bluenose/rmlistitem/%']=array(
  'page callback' => 'bluenose_app_rmelement',
  'page arguments' => array(1, 2),
  'access arguments' => array ('access bluenose_app wishlists'),
  'type' => MENU_LOCAL_TASK,
);
$items['bluenose/rmavailableprop/%']=array(
  'page callback' => 'bluenose_app_rmelement',
  'page arguments' => array(1, 2),
  'access arguments' => array ('access bluenose_app wishlists'),
  'type' => 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!

  • Are you just asking for a review of the code? Why do you route through bluenose_app_rmelement() instead of calling the correct callback in hook_menu()? – Chris Rockwell Apr 21 '15 at 2:54
  • Yeah, basically. Drupals different from other frameworks, and most of what I know comes from classic OOP and js. So I was wondering if I'm trashing the way drupal works :) The switch router seemed to be a little more maintainable to me. In practice, there are far more of these methods, and they're all identical except for the actual crud they call. My cpd would go crazy over that. Is there reason to bypass the switch and call crud directly? If I were to keep it as is, I'm sure I'd reduce it to a single menu registry and pass two arbitrary values - essentially bypassing hook_menu when it comes – Kevin Ard Apr 21 '15 at 3:02
  • There's other validation going on in rm*. Eg: does this user own this item, etc. That's the part that got redundant – Kevin Ard Apr 21 '15 at 3:09
0

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' => 'bluenose_app_rmelement',
  'page arguments' => array(1, 2),
  'access arguments' => array ('access bluenose_app wishlists'),
  'type' => 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().

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.