Skip to content

Instantly share code, notes, and snippets.

@twfahey1
Created November 28, 2022 17:35
Show Gist options
  • Save twfahey1/46adee1f30a6f132ef3ca844fa9616c1 to your computer and use it in GitHub Desktop.
Save twfahey1/46adee1f30a6f132ef3ca844fa9616c1 to your computer and use it in GitHub Desktop.
Drupal 8, 9+ - Loading a menu programmatically

Example loading a menu programmatically in Drupal

In this example, it's a plugin block. The magic takes place in the build() method of the block. In this example, we're loading all the main menu entities, and teasing out the plain URL, passing that through to our JS layer. Of course, anything could be done with the data once it's been fetched this way.

<?php
namespace Drupal\example_menu\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an example main menu block.
*
* @Block(
* id = "example_menu_example_main_menu",
* admin_label = @Translation("Example Main Menu"),
* category = @Translation("Custom")
* )
*/
class ExampleMainMenuBlock extends BlockBase implements ContainerFactoryPluginInterface
{
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new ExampleMainMenuBlock instance.
*
* @param array $configuration
* The plugin configuration, i.e. an array with configuration values keyed
* by configuration option name. The special key 'context' may be used to
* initialize the defined contexts by setting it to an array of context
* values keyed by context names.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager)
{
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition)
{
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration()
{
return [
'menu_to_use' => 'main',
];
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state)
{
$form['menu_to_use'] = [
'#type' => 'textfield',
'#title' => $this->t('Menu to use (machine name)'),
'#default_value' => $this->configuration['menu_to_use'],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state)
{
$this->configuration['menu_to_use'] = $form_state->getValue('menu_to_use');
}
/**
* {@inheritdoc}
*/
public function build()
{
$build['content'] = [
'#theme' => 'example_menu',
'#items' => [],
'#attributes' => [],
];
// Attach the library.
$build['#attached']['library'][] = 'example_menu/example_menu';
// Attach drupalSettings for the JS.
$menu_name = $this->configuration['menu_to_use'];
$menu_entity = $this->entityTypeManager->getStorage('menu')->load($menu_name);
if (!empty($menu_entity)) {
$menu_tree = \Drupal::menuTree();
$parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
$parameters->maxDepth = 2;
$tree = $menu_tree->load($menu_name, $parameters);
$manipulators = array(
array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
);
$tree = $menu_tree->transform($tree, $manipulators);
$menu = $menu_tree->build($tree);
// The raw variable of menu entity data. We can perform any necessary massage / alters.
$massaged_menu_items = $menu['#items'];
// Massage each items url attribute, which is a Drupal\Core\Url object. We want to add on an attribute "url_plain" which is the plain url string.
foreach ($massaged_menu_items as $key => $item) {
$massaged_menu_items[$key]['url_plain'] = $this->extractUrlPlain($item);
// If there is a below menu, massage each item there too.
if (isset($massaged_menu_items[$key]['below'])) {
foreach ($massaged_menu_items[$key]['below'] as $below_key => $below_item) {
$massaged_menu_items[$key]['below'][$below_key]['url_plain'] = $this->extractUrlPlain($below_item);
}
}
}
$build['content']['#items'] = $massaged_menu_items;
// Pass menu items down to our JS layer.
$build['#attached']['drupalSettings']['exampleMenu'] = [
'items' => $massaged_menu_items,
];
// Add cache tags for menu entity.
$build['#cache']['tags'] = $menu_entity->getCacheTags();
} else {
$build['content'] = [
'#markup' => 'Uh oh - Example Menu is configured to an invalid menu.'
];
}
return $build;
}
/**
* A helper function to extract out the url plain.
*
* @param \Drupal\Core\Url $item
* The URL item to extract the value from.
*/
private function extractUrlPlain($item)
{
return $item['url']->toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment