- 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
about 1 year ago 12:02am 7 September 2023 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
about 1 year ago 10:36am 7 September 2023 - π§πͺ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 theMenuTreeStorage
class, we were able to replace it with theMenuLinkManager
.- $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
about 1 year ago 5:03pm 7 September 2023 - π¬π§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.
- πΊπΈ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)