\Drupal::services('menu.tree_storage') non-existent.

Created on 6 September 2023, over 1 year ago
Updated 17 November 2023, about 1 year ago

Problem/Motivation

The Service "menu.tree_storage" is declared in core.services.yml, and while it works in D9.5 and D10.0, it fails in D10.1 with the following error message:

Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: You have requested a non-existent service "menu.tree_storage". in Drupal\Component\DependencyInjection\Container->get() (line 157 of core/lib/Drupal/Component/DependencyInjection/Container.php).

Steps to reproduce

Create a basic custom module, and try to call $test = \Drupal::service('menu.tree_storage');

Or, install Devel, and run drush devel:services | grep menu -- the service does NOT show up in the list.

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

πŸ’¬ Support request
Status

Closed: works as designed

Version

10.1 ✨

Component
Menu systemΒ  β†’

Last updated about 16 hours ago

Created by

πŸ‡ΊπŸ‡ΈUnited States dustinsilva@gmail.com

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @dustinsilva@gmail.com
  • πŸ‡ΊπŸ‡ΈUnited States dustinsilva@gmail.com

    So after further digging, I found setting core\core.services.yml public: TRUE for menu.tree_storage fixes the issue.

    Where I am hung up is why D9.5 and D10 also had public: false but the code worked, and the service shows up in devel:services.

    Was this "fixed" in D10.1, and that's why it no longer works, or, is there something else I am missing.

  • Status changed to Postponed: needs info over 1 year ago
  • I just installed Drupal 9.5.9 and that service is definitely not publicly-accessible on it.

    ddev exec drush devel:services |grep tree_storage
    

    returns nothing. So this bug report needs more information. Private services are supposed to be privateβ€”meaning, injectable only. This is a performance optimization.

  • Status changed to Active over 1 year ago
  • πŸ‡§πŸ‡ͺBelgium jelle_s Antwerp, Belgium

    The problem occurs when trying to inject the service in a plugin like for example a block. This is because of the public static function create pattern I assume, since that technically doesn't count as "injectable" I guess.

    Example code:

    class CrisisCommunicationBlock extends DgCrisisCrisisCommunicationBlock {
    
      // ...
    
      /**
       * Class constructor.
       *
       * @param array $configuration
       *   The block configuration.
       * @param string $plugin_id
       *   The plugin id.
       * @param array $plugin_definition
       *   The plugin definition.
       * @param \Drupal\Core\Menu\MenuTreeStorageInterface $menu_tree_storage
       *   The menu tree storage service.
       */
      public function __construct(array $configuration, $plugin_id, array $plugin_definition, MenuTreeStorageInterface $menu_tree_storage) {
        parent::__construct($configuration, $plugin_id, $plugin_definition);
        $this->menuTreeStorage = $menu_tree_storage;
      }
    
      /**
       * {@inheritdoc}
       */
      public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): DgCrisisCrisisCommunicationBlock {
        return new static(
          $configuration,
          $plugin_id,
          $plugin_definition,
          $container->get('menu.tree_storage'),
        );
      }
    

    This did work on Drupal 9.

  • πŸ‡§πŸ‡ͺBelgium jelle_s Antwerp, Belgium

    In case this helps someone else: Since we only use the load method of the MenuTreeStorage class, we were able to replace it with the MenuLinkManager.

    -    $container->get('menu.tree_storage'),
    +    $container->get('plugin.manager.menu.link'),
    -    $menu_link = $this->menuTreeStorage->load($id);
    +    $menu_link = $this->menuLinkManager->getDefinition($id, FALSE);
    
  • This is an intentional change in πŸ“Œ Enable service autowiring by adding interface aliases to core service definitions Fixed and it is in the 10.1.0 release notes β†’ .

    A bug in Drupal's dependency injection container is fixed. The bug could allow certain private services to be accessed by $container->get() depending on code execution order. Custom or contributed module code accessing services in this way would have been fragile before the change, but will now always break. Public services are unaffected.

  • Status changed to Closed: works as designed over 1 year ago
  • πŸ‡¬πŸ‡§United Kingdom longwave UK

    As per #7 I consider this "works as designed", the service was never designed to be public and the fact you could access it directly before (in some circumstances at least) was a bug.

  • πŸ‡ΊπŸ‡ΈUnited States luke.leber Pennsylvania

    How might a user replace a call to MenuTreeStorageInterface::loadAllChildren?

    This interface was never marked @internal. :-(

  • πŸ‡§πŸ‡ͺBelgium jelle_s Antwerp, Belgium

    @Luke.Leber best I can tell is you can create your own service, inject it as a parameter in that service through your services.yml and call it from there, basically wrapping the non-public service into a public service

  • πŸ‡ΊπŸ‡ΈUnited States maskedjellybean Portland, OR

    Has anyone who needs to use methods from the now unusable service figured out a workaround? Can you post an example here of wrapping the now private service in a public service? I need to use \Drupal\Core\Menu\MenuTreeStorage::getRootPathIds.

  • πŸ‡ΊπŸ‡ΈUnited States maskedjellybean Portland, OR

    Hmm actually I think #6 works for this situation too.

  • πŸ‡§πŸ‡ͺBelgium jelle_s Antwerp, Belgium

    Wrapping it would look something like this:

    mymodule.services.yml

    mymodule.tree_storage_wrapper:
        class: Drupal\mymodule\TreeStorageWrapper
        arguments: ['@menu.tree_storage']
    
    namespace Drupal\mymodule;
    
    use \Drupal\Core\Menu\MenuTreeStorageInterface;
    
    class TreeStorageWrapper {
    
      protected $menuTreeStorage;
    
      public function __construct(MenuTreeStorageInterface $menu_tree_storage) {
        $this->menuTreeStorage = $menu_tree_storage;
      }
    
      // Define the methods you need to use the menu tree storage for.
    }
    

    This is untested code, but roughly how it would be done.

  • πŸ‡ΊπŸ‡ΈUnited States maskedjellybean Portland, OR

    Thank you for the example! I don't understand how it's an improvement over accessing the service directly, but that's ok.

  • πŸ‡§πŸ‡ͺBelgium jelle_s Antwerp, Belgium

    @maskedjellybean I don't know the technical details, but according to #4 it's a performance optimization

  • πŸ‡ΊπŸ‡ΈUnited States maskedjellybean Portland, OR

    I did end up needing the wrapper service. Here's a complete example:

    In addition to the code in #13, you will need a getter method:

      /**
       * Gets Menu Tree Storage service.
       *
       * @return \Drupal\Core\Menu\MenuTreeStorageInterface
       */
      public function getMenuTreeStorage(): MenuTreeStorageInterface {
        return $this->menuTreeStorage;
      }
    

    Then inject your new service where it's needed instead of the MenuLinkStorage service.

    Use it like this:
    $this->treeStorageWrapper->getMenuTreeStorage()->getRootPathIds($plugin_id)

Production build 0.71.5 2024