How to avoid url conflict when defining menu items, and make sure path without wildcards will be used in favor of path with %? In hook_menu, I have

$path = "events"

$items[$path] = array(
  'title' =&gt; t('Events'),
  'page callback' =&gt; 'my_events_page',
  'access arguments' =&gt; array('view my events'),
  'type' =&gt; MENU_NORMAL_ITEM,
  'file' =&gt; 'my_events.pages.inc',
);

$items[$path . '/past'] = array(
  'title' =&gt; t('Past events'),
  'page callback' =&gt; 'my_events_past_page',
  'access arguments' =&gt; array('view my events'),
  'type' =&gt; MENU_NORMAL_ITEM,
  'file' =&gt; 'my_events.pages.inc',
);

$items[$path . '/%'] = array(
  'title' =&gt; t('Events'),
  'page callback' =&gt; 'my_events_page',
  'page arguments' =&gt; array($depth),
  'access arguments' =&gt; array('view my events'),
  'type' =&gt; MENU_CALLBACK,
  'file' =&gt; 'my_events.pages.inc',
);


They were working, but not now.

If I change $items['events/past'] to $items['events/past1'], it works.

Does it mean I have path conflict? If so, how can I resolve it?

Rows in my {menu_router} table are as follows:


path: events
page_callback: views_page (but I set it to xxx_events_page in hook_menu)
fit: 1
include_file: empty (should not be empty)
--
path: events/%
page_callback: xxx_events_page (good)
fit: 2
include_file: a file path is there
--
path: events/past
page_callback: views_page (but I set it to xxx_events_past_page in hook_menu)
fit: 3
include_file: empty (should not be empty)


If I update the page_callback and include_file it works. If I clear the cache, everything set back to default.
Assuming you are using Drupal 7:

Order matters only a bit - menu_router_build() saves all path into the {menu_router} table just the way they are set. But there are no guarantees database will return elements in that order. Logic to decide which menu item should be executed is in menu_get_item().

Actual query is:

$router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' =&gt; $ancestors))-&gt;fetchAssoc();


As you can see, fitting elements are ordered by fit column, most specific first.

As you can see in actual _menu_router_build(), $fit gets initially set to 0 for each menu item:

foreach ($callbacks as $path =&gt; $item) {
  $load_functions = array();
  $to_arg_functions = array();
  $fit = 0;


It means it shouldn't be possible for the order of menu elements in hook_menu() to affect order of routing. Actual code is a bit complicated to understand, but elements specific should have higher fit than elements with '%' in them. As the comment in code says: // If there is no %, it fits maximally.