Hello
I'm trying to upgrade a module from D7 and I'm facing some concerns about getting the main menu links.
I read the change list where they said we want to use : MenuLinkTreeInterface::load()
so I tried this :
use Drupal\Core\Menu\MenuLinkTreeInterface;
...
function xxx() {
$mainmenu = MenuLinkTreeInterface::load('main');

But I've got an error :
Cannot call abstract method Drupal\Core\Menu\MenuLinkTreeInterface::load()
that I couldn't solve.

After that I tried :

$mainmenu = menu_load_links($menu_name);

But I've got another error (undefined function although it's not a deprecated function I think...)
How can I get my links list from the main-menu (now called 'main' if I'm correct...) ?
Any idea welcome...

Comments

arnoldbird’s picture

Is this it: https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Menu!menu.api.php...

Particularly the code starting with...
$menu_tree = \Drupal::menuTree();

artatum’s picture

Thx for helping me. I tried this path and I get (with ksm($menu)) a huge output whose I cant find anything useful : how to eventually get label + link ?
I printed $menu_html as well and can see the menu links : this is the output
$args array(1)
→Called from /drupal8/modules/contrib/devel/kint/kint.module:55 [ksm()]
stdClass Object
(
[__CLASS__] => Drupal\Core\Render\Markup
[string] =>
Fancy (displayed as a link)
Home ...
)
But this doesn't help me to figure out the structure and how to browse into : where is burried the url, for instance ?

artatum’s picture

After diving in the ksm print I found the 'internalPath' and 'title' properties :

    ksm ($menu['#items']['menu_link_content:c4017fbc-d067-4df2-9fcd-f7e3e09ab822']['title']);
    ksm ($menu['#items']['menu_link_content:c4017fbc-d067-4df2-9fcd-f7e3e09ab822']['url']->internalPath);

I tried to print them. I had an error because of protected status for internalPath :
Cannot access protected property Drupal\Core\Url::$internalPath
1 - I found ['menu_link_content:c4017fbc-d067-4df2-9fcd-f7e3e09ab822'] a little ... crazy !
2 - What can I do for this error and grab the url ?
3 - Is there any function to get some old good array ? Something like :

my_array= simply_get_links_from_a_menu_given_its_name($menu_name);
arnoldbird’s picture

I've spent a good bit of time trying to find a way, and have come up empty thus far. I can't seem to find an example anywhere in the code. I can only find examples where menus are rendered, edited, etc. But none of that code seems to work for accessing the link tree as was possible (and easy) in D7. No doubt it will be easy in D8 too, once we know how it's done. There might be some clues at... https://www.drupal.org/node/2302069 -- but I looked at some similar docs that ultimately did not help.

But I am finding that my browser crashes whenever I try to access the links in a "foreach ($tree as $element)" loop. Trying to var_dump a link crashes my browser, so it looks like I'll need some other way to debug in classes.

If you figure out how to access the links in the tree, please post your solution.

artatum’s picture

Hi
I wish you a Happy New Drupal Year Arnoldbird !
I followed up the code you showed me and edited it a little bit : It's yet not perfect (especially about front ) but it works : you get an indexed array as output:
$title => $link

function get_menu_items($menu_name) {
  $menu_tree = \Drupal::menuTree();
// Build the typical default set of menu tree parameters.
  $parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
// Load the tree based on this set of parameters.
  $tree = $menu_tree->load($menu_name, $parameters);
// Transform the tree using the manipulators you want.
  $manipulators = array(
  // Only show links that are accessible for the current user.
    array('callable' => 'menu.default_tree_manipulators:checkAccess'),
  // Use the default sorting of menu links.
    array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
  );
  $tree = $menu_tree->transform($tree, $manipulators);
  // Finally, build a renderable array from the transformed tree.
  $menu_tmp = $menu_tree->build($tree);
  //ksm ($menu_tmp);       
  $menu = array();
  foreach ($menu_tmp['#items'] as $item) {
    if ($item['url']->getRouteName() == 'front') {
     $menu[] = [$item['title'] => $item['url']->getRouteName()];
    } else {
     $menu[] = [$item['title'] => $item['url']->getInternalPath()];
    }
  }
  return $menu;
}

NB in :
if ($item['url']->getRouteName() == 'front') {
you need inf and sup around front, as usual (the actual editor deletes them)

artatum’s picture

For debugging you need kint.

arnoldbird’s picture

Nice! Pasted it into my module, and it works just like you have it. I might just return $menu and then have a few functions for common ways I'll need to loop through that array.

function mymodule_get_menu($menu_name) {
  
  $menu_tree = \Drupal::menuTree();
  // Build the typical default set of menu tree parameters.
  $parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
  // Load the tree based on this set of parameters.
  $tree = $menu_tree->load($menu_name, $parameters);
  // Transform the tree using the manipulators you want.
  $manipulators = array(
  // Only show links that are accessible for the current user.
  array('callable' => 'menu.default_tree_manipulators:checkAccess'),
  // Use the default sorting of menu links.
  array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
  );
  $tree = $menu_tree->transform($tree, $manipulators);
  // Finally, build a renderable array from the transformed tree.
  $menu = $menu_tree->build($tree);
  
  return $menu;
}

function mymodule_get_menu_items($menu_name) {
  
  $menu_data = mymodule_get_menu($menu_name);
  
  foreach ($menu_data['#items'] as $item) {
    if ($item['url']->getRouteName() == '') {
      $menu[] = [$item['title'] => $item['url']->getRouteName()];
    }
    else {
      $menu[] = [$item['title'] => $item['url']->getInternalPath()];
    }
  }
  
  return $menu;
}
AmolB’s picture

It would be better if it works for sub navigation items also.
I am trying on that.
Nice job.

davidwhthomas’s picture

Just noting another way to get the menu links:

$menu_name = 'main';
$storage = \Drupal::entityManager()->getStorage('menu_link_content');
$menu_links = $storage->loadByProperties(['menu_name' => $menu_name]);

For example:

/**
 * Get menu link data by menu name
 * @param  [string] $menu_name [menu name]
 * @return [array]            [menu links]
 */
public function menuLoadLinks($menu_name) {
  $links = [];
  $storage = \Drupal::entityManager()->getStorage('menu_link_content');
  $menu_links = $storage->loadByProperties(['menu_name' => $menu_name]);
  if (empty($menu_links)) return $links;
  foreach ($menu_links as $mlid => $menu_link) {
    $link = [];
    $link['type'] = 'menu_link';
    $link['mlid'] = $menu_link->id->value;
    $link['plid'] = $menu_link->parent->value ?? '0';
    $link['menu_name'] = $menu_link->menu_name->value;
    $link['link_title'] = $menu_link->title->value;
    $link['uri'] = $menu_link->link->uri;
    $link['options'] = $menu_link->link->options;
    $link['weight'] = $menu_link->weight->value;
    $links[] = $link;
  }
  // Sort menu links by weight element
  usort($links, [SortArray::class, 'sortByWeightElement']);
  return $links;
}
Debasish147’s picture

public function getMenu($menu_name) {
  $tree = $this->loadMenu($menu_name);
  return $this->constructMenu($tree);
}

public function loadMenu($menu_name) {
  $menu_tree = $this->menuTree;
  $parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
  $tree = $menu_tree->load($menu_name, $parameters);

  $manipulators = [
    ['callable' => 'menu.default_tree_manipulators:checkAccess'],
    ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
  ];
  return $menu_tree->transform($tree, $manipulators);
}

public function constructMenu($tree) {
  $items = [];

  foreach ($tree as $element) {
    $link = $element->link;
    $record = $link->getPluginDefinition();
    $menu_title = $record['title'];
    $menu_id = $record['metadata']['entity_id'];
    $items[$menu_id] = $menu_title;
    if ($element->subtree) {
      $items = array_merge($items, $this->constructMenu($element->subtree));
    }
  }
  return $items;
}
n8tron’s picture

function get_menu_items($menu_name) {
  $menu_tree = \Drupal::menuTree();
  // Build the typical default set of menu tree parameters.
  $parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
  // Load the tree based on this set of parameters.
  $tree = $menu_tree->load($menu_name, $parameters);
  // Transform the tree using the manipulators you want.
  $manipulators = [
    // Only show links that are accessible for the current user.
    ['callable' => 'menu.default_tree_manipulators:checkAccess'],
    // Use the default sorting of menu links.
    ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
  ];
  $tree = $menu_tree->transform($tree, $manipulators);
  // Finally, build a renderable array from the transformed tree.
  $menu_tmp = $menu_tree->build($tree);
  //dump ($menu_tmp);
  $menu = [];
  foreach ($menu_tmp['#items'] as $item) {
    $childItems = [];
    if (!empty($item['below'])) {
      foreach ($item['below'] as $child) {
        $childItems[] = [
          'href' => $child['url']->toString(),
          'label' => $child['title'],
        ];
      }
    }
    $menu[] = [
      'href' => $item['url']->toString(),
      'label' => $item['title'],
      'children' => !empty($item['below']) ? $childItems : ''
    ];
  }

  return $menu;
}
sushant bhandi’s picture

n8tron is there any way i can get all the levels of the menu using this code or using recursive function. this code only works for 1 level

Kasey_MK’s picture

https://drupal.stackexchange.com/questions/252503/can-menutree-load-a-menus-items-including-children has info about getting children items, too, by changing the $parameters.