class MenuLinkManager

Same name in other branches
  1. 8.9.x core/lib/Drupal/Core/Menu/MenuLinkManager.php \Drupal\Core\Menu\MenuLinkManager
  2. 10 core/lib/Drupal/Core/Menu/MenuLinkManager.php \Drupal\Core\Menu\MenuLinkManager
  3. 11.x core/lib/Drupal/Core/Menu/MenuLinkManager.php \Drupal\Core\Menu\MenuLinkManager

Manages discovery, instantiation, and tree building of menu link plugins.

This manager finds plugins that are rendered as menu links.

Hierarchy

Expanded class hierarchy of MenuLinkManager

1 string reference to 'MenuLinkManager'
core.services.yml in core/core.services.yml
core/core.services.yml
1 service uses MenuLinkManager
plugin.manager.menu.link in core/core.services.yml
Drupal\Core\Menu\MenuLinkManager

File

core/lib/Drupal/Core/Menu/MenuLinkManager.php, line 18

Namespace

Drupal\Core\Menu
View source
class MenuLinkManager implements MenuLinkManagerInterface {
    
    /**
     * Provides some default values for the definition of all menu link plugins.
     *
     * @todo Decide how to keep these field definitions in sync.
     *   https://www.drupal.org/node/2302085
     *
     * @var array
     */
    protected $defaults = [
        // (required) The name of the menu for this link.
'menu_name' => 'tools',
        // (required) The name of the route this links to, unless it's external.
'route_name' => '',
        // Parameters for route variables when generating a link.
'route_parameters' => [],
        // The external URL if this link has one (required if route_name is empty).
'url' => '',
        // The static title for the menu link. If this came from a YAML definition
        // or other safe source this may be a TranslatableMarkup object.
'title' => '',
        // The description. If this came from a YAML definition or other safe source
        // this may be a TranslatableMarkup object.
'description' => '',
        // The plugin ID of the parent link (or NULL for a top-level link).
'parent' => '',
        // The weight of the link.
'weight' => 0,
        // The default link options.
'options' => [],
        'expanded' => 0,
        'enabled' => 1,
        // The name of the module providing this link.
'provider' => '',
        'metadata' => [],
        // Default class for local task implementations.
'class' => 'Drupal\\Core\\Menu\\MenuLinkDefault',
        'form_class' => 'Drupal\\Core\\Menu\\Form\\MenuLinkDefaultForm',
        // The plugin ID. Set by the plugin system based on the top-level YAML key.
'id' => '',
    ];
    
    /**
     * The object that discovers plugins managed by this manager.
     *
     * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
     */
    protected $discovery;
    
    /**
     * The object that instantiates plugins managed by this manager.
     *
     * @var \Drupal\Component\Plugin\Factory\FactoryInterface
     */
    protected $factory;
    
    /**
     * The menu link tree storage.
     *
     * @var \Drupal\Core\Menu\MenuTreeStorageInterface
     */
    protected $treeStorage;
    
    /**
     * Service providing overrides for static links.
     *
     * @var \Drupal\Core\Menu\StaticMenuLinkOverridesInterface
     */
    protected $overrides;
    
    /**
     * The module handler.
     *
     * @var \Drupal\Core\Extension\ModuleHandlerInterface
     */
    protected $moduleHandler;
    
    /**
     * Constructs a \Drupal\Core\Menu\MenuLinkManager object.
     *
     * @param \Drupal\Core\Menu\MenuTreeStorageInterface $tree_storage
     *   The menu link tree storage.
     * @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $overrides
     *   The service providing overrides for static links.
     * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
     *   The module handler.
     */
    public function __construct(MenuTreeStorageInterface $tree_storage, StaticMenuLinkOverridesInterface $overrides, ModuleHandlerInterface $module_handler) {
        $this->treeStorage = $tree_storage;
        $this->overrides = $overrides;
        $this->moduleHandler = $module_handler;
    }
    
    /**
     * Performs extra processing on plugin definitions.
     *
     * By default we add defaults for the type to the definition. If a type has
     * additional processing logic, the logic can be added by replacing or
     * extending this method.
     *
     * @param array $definition
     *   The definition to be processed and modified by reference.
     * @param $plugin_id
     *   The ID of the plugin this definition is being used for.
     */
    protected function processDefinition(array &$definition, $plugin_id) {
        $definition = NestedArray::mergeDeep($this->defaults, $definition);
        // Typecast so NULL, no parent, will be an empty string since the parent ID
        // should be a string.
        $definition['parent'] = (string) $definition['parent'];
        $definition['id'] = $plugin_id;
    }
    
    /**
     * Gets the plugin discovery.
     *
     * @return \Drupal\Component\Plugin\Discovery\DiscoveryInterface
     */
    protected function getDiscovery() {
        if (!isset($this->discovery)) {
            $yaml_discovery = new YamlDiscovery('links.menu', $this->moduleHandler
                ->getModuleDirectories());
            $yaml_discovery->addTranslatableProperty('title', 'title_context');
            $yaml_discovery->addTranslatableProperty('description', 'description_context');
            $this->discovery = new ContainerDerivativeDiscoveryDecorator($yaml_discovery);
        }
        return $this->discovery;
    }
    
    /**
     * Gets the plugin factory.
     *
     * @return \Drupal\Component\Plugin\Factory\FactoryInterface
     */
    protected function getFactory() {
        if (!isset($this->factory)) {
            $this->factory = new ContainerFactory($this);
        }
        return $this->factory;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getDefinitions() {
        // Since this function is called rarely, instantiate the discovery here.
        $definitions = $this->getDiscovery()
            ->getDefinitions();
        $this->moduleHandler
            ->alter('menu_links_discovered', $definitions);
        foreach ($definitions as $plugin_id => &$definition) {
            $definition['id'] = $plugin_id;
            $this->processDefinition($definition, $plugin_id);
        }
        // If this plugin was provided by a module that does not exist, remove the
        // plugin definition.
        // @todo Address what to do with an invalid plugin.
        //   https://www.drupal.org/node/2302623
        foreach ($definitions as $plugin_id => $plugin_definition) {
            if (!empty($plugin_definition['provider']) && !$this->moduleHandler
                ->moduleExists($plugin_definition['provider'])) {
                unset($definitions[$plugin_id]);
            }
        }
        return $definitions;
    }
    
    /**
     * {@inheritdoc}
     */
    public function rebuild() {
        $definitions = $this->getDefinitions();
        // Apply overrides from config.
        $overrides = $this->overrides
            ->loadMultipleOverrides(array_keys($definitions));
        foreach ($overrides as $id => $changes) {
            if (!empty($definitions[$id])) {
                $definitions[$id] = $changes + $definitions[$id];
            }
        }
        $this->treeStorage
            ->rebuild($definitions);
    }
    
    /**
     * {@inheritdoc}
     */
    public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
        $definition = $this->treeStorage
            ->load($plugin_id);
        if (empty($definition) && $exception_on_invalid) {
            throw new PluginNotFoundException($plugin_id);
        }
        return $definition;
    }
    
    /**
     * {@inheritdoc}
     */
    public function hasDefinition($plugin_id) {
        return (bool) $this->getDefinition($plugin_id, FALSE);
    }
    
    /**
     * Returns a pre-configured menu link plugin instance.
     *
     * @param string $plugin_id
     *   The ID of the plugin being instantiated.
     * @param array $configuration
     *   An array of configuration relevant to the plugin instance.
     *
     * @return \Drupal\Core\Menu\MenuLinkInterface
     *   A menu link instance.
     *
     * @throws \Drupal\Component\Plugin\Exception\PluginException
     *   If the instance cannot be created, such as if the ID is invalid.
     */
    public function createInstance($plugin_id, array $configuration = []) {
        return $this->getFactory()
            ->createInstance($plugin_id, $configuration);
    }
    
    /**
     * {@inheritdoc}
     */
    public function getInstance(array $options) {
        if (isset($options['id'])) {
            return $this->createInstance($options['id']);
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function deleteLinksInMenu($menu_name) {
        foreach ($this->treeStorage
            ->loadByProperties([
            'menu_name' => $menu_name,
        ]) as $plugin_id => $definition) {
            $instance = $this->createInstance($plugin_id);
            if ($instance->isDeletable()) {
                $this->deleteInstance($instance, TRUE);
            }
            elseif ($instance->isResettable()) {
                $new_instance = $this->resetInstance($instance);
                $affected_menus[$new_instance->getMenuName()] = $new_instance->getMenuName();
            }
        }
    }
    
    /**
     * Deletes a specific instance.
     *
     * @param \Drupal\Core\Menu\MenuLinkInterface $instance
     *   The plugin instance to be deleted.
     * @param bool $persist
     *   If TRUE, calls MenuLinkInterface::deleteLink() on the instance.
     *
     * @throws \Drupal\Component\Plugin\Exception\PluginException
     *   If the plugin instance does not support deletion.
     */
    protected function deleteInstance(MenuLinkInterface $instance, $persist) {
        $id = $instance->getPluginId();
        if ($instance->isDeletable()) {
            if ($persist) {
                $instance->deleteLink();
            }
        }
        else {
            throw new PluginException("Menu link plugin with ID '{$id}' does not support deletion");
        }
        $this->treeStorage
            ->delete($id);
    }
    
    /**
     * {@inheritdoc}
     */
    public function removeDefinition($id, $persist = TRUE) {
        $definition = $this->treeStorage
            ->load($id);
        // It's possible the definition has already been deleted, or doesn't exist.
        if ($definition) {
            $instance = $this->createInstance($id);
            $this->deleteInstance($instance, $persist);
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function menuNameInUse($menu_name) {
        $this->treeStorage
            ->menuNameInUse($menu_name);
    }
    
    /**
     * {@inheritdoc}
     */
    public function countMenuLinks($menu_name = NULL) {
        return $this->treeStorage
            ->countMenuLinks($menu_name);
    }
    
    /**
     * {@inheritdoc}
     */
    public function getParentIds($id) {
        if ($this->getDefinition($id, FALSE)) {
            return $this->treeStorage
                ->getRootPathIds($id);
        }
        return NULL;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getChildIds($id) {
        if ($this->getDefinition($id, FALSE)) {
            return $this->treeStorage
                ->getAllChildIds($id);
        }
        return NULL;
    }
    
    /**
     * {@inheritdoc}
     */
    public function loadLinksByRoute($route_name, array $route_parameters = [], $menu_name = NULL) {
        $instances = [];
        $loaded = $this->treeStorage
            ->loadByRoute($route_name, $route_parameters, $menu_name);
        foreach ($loaded as $plugin_id => $definition) {
            $instances[$plugin_id] = $this->createInstance($plugin_id);
        }
        return $instances;
    }
    
    /**
     * {@inheritdoc}
     */
    public function addDefinition($id, array $definition) {
        if ($this->treeStorage
            ->load($id)) {
            throw new PluginException("The menu link ID {$id} already exists as a plugin definition");
        }
        elseif ($id === '') {
            throw new PluginException("The menu link ID cannot be empty");
        }
        // Add defaults, so there is no requirement to specify everything.
        $this->processDefinition($definition, $id);
        // Store the new link in the tree.
        $this->treeStorage
            ->save($definition);
        return $this->createInstance($id);
    }
    
    /**
     * {@inheritdoc}
     */
    public function updateDefinition($id, array $new_definition_values, $persist = TRUE) {
        $instance = $this->createInstance($id);
        if ($instance) {
            $new_definition_values['id'] = $id;
            $changed_definition = $instance->updateLink($new_definition_values, $persist);
            $this->treeStorage
                ->save($changed_definition);
        }
        return $instance;
    }
    
    /**
     * {@inheritdoc}
     */
    public function resetLink($id) {
        $instance = $this->createInstance($id);
        $new_instance = $this->resetInstance($instance);
        return $new_instance;
    }
    
    /**
     * Resets the menu link to its default settings.
     *
     * @param \Drupal\Core\Menu\MenuLinkInterface $instance
     *   The menu link which should be reset.
     *
     * @return \Drupal\Core\Menu\MenuLinkInterface
     *   The reset menu link.
     *
     * @throws \Drupal\Component\Plugin\Exception\PluginException
     *   Thrown when the menu link is not resettable.
     */
    protected function resetInstance(MenuLinkInterface $instance) {
        $id = $instance->getPluginId();
        if (!$instance->isResettable()) {
            throw new PluginException("Menu link {$id} is not resettable");
        }
        // Get the original data from disk, reset the override and re-save the menu
        // tree for this link.
        $definition = $this->getDefinitions()[$id];
        $this->overrides
            ->deleteOverride($id);
        $this->treeStorage
            ->save($definition);
        return $this->createInstance($id);
    }
    
    /**
     * {@inheritdoc}
     */
    public function resetDefinitions() {
        $this->treeStorage
            ->resetDefinitions();
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
MenuLinkManager::$defaults protected property Provides some default values for the definition of all menu link plugins.
MenuLinkManager::$discovery protected property The object that discovers plugins managed by this manager.
MenuLinkManager::$factory protected property The object that instantiates plugins managed by this manager.
MenuLinkManager::$moduleHandler protected property The module handler.
MenuLinkManager::$overrides protected property Service providing overrides for static links.
MenuLinkManager::$treeStorage protected property The menu link tree storage.
MenuLinkManager::addDefinition public function Adds a new menu link definition to the menu tree storage. Overrides MenuLinkManagerInterface::addDefinition
MenuLinkManager::countMenuLinks public function Counts the total number of menu links. Overrides MenuLinkManagerInterface::countMenuLinks
MenuLinkManager::createInstance public function Returns a pre-configured menu link plugin instance.
MenuLinkManager::deleteInstance protected function Deletes a specific instance.
MenuLinkManager::deleteLinksInMenu public function Deletes all links having a certain menu name. Overrides MenuLinkManagerInterface::deleteLinksInMenu
MenuLinkManager::getChildIds public function Loads all child link IDs of a given menu link, regardless of visibility. Overrides MenuLinkManagerInterface::getChildIds
MenuLinkManager::getDefinition public function
MenuLinkManager::getDefinitions public function
MenuLinkManager::getDiscovery protected function Gets the plugin discovery.
MenuLinkManager::getFactory protected function Gets the plugin factory.
MenuLinkManager::getInstance public function
MenuLinkManager::getParentIds public function Loads all parent link IDs of a given menu link. Overrides MenuLinkManagerInterface::getParentIds
MenuLinkManager::hasDefinition public function
MenuLinkManager::loadLinksByRoute public function Loads multiple plugin instances based on route. Overrides MenuLinkManagerInterface::loadLinksByRoute
MenuLinkManager::menuNameInUse public function Determines if any links use a given menu name. Overrides MenuLinkManagerInterface::menuNameInUse
MenuLinkManager::processDefinition protected function Performs extra processing on plugin definitions.
MenuLinkManager::rebuild public function Triggers discovery, save, and cleanup of discovered links. Overrides MenuLinkManagerInterface::rebuild
MenuLinkManager::removeDefinition public function Removes a single link definition from the menu tree storage. Overrides MenuLinkManagerInterface::removeDefinition
MenuLinkManager::resetDefinitions public function Resets any local definition cache. Used for testing. Overrides MenuLinkManagerInterface::resetDefinitions
MenuLinkManager::resetInstance protected function Resets the menu link to its default settings.
MenuLinkManager::resetLink public function Resets the values for a menu link based on the values found by discovery. Overrides MenuLinkManagerInterface::resetLink
MenuLinkManager::updateDefinition public function Updates the values for a menu link definition in the menu tree storage. Overrides MenuLinkManagerInterface::updateDefinition
MenuLinkManager::__construct public function Constructs a \Drupal\Core\Menu\MenuLinkManager object.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.