Problem/Motivation

When adding optional integrations with contrib modules, it is common to use the "_module_dependencies" requirement on a declared route.
For example, a Devel integration for commerce_store:

commerce.store_devel:
  path: '/admin/commerce/config/store/{commerce_store}/devel'
  defaults:
    _content: '\Drupal\commerce\Controller\CommerceDevelController::storeLoad'
    _title: 'Dump a store'
  options:
    _admin_route: TRUE
  requirements:
    _module_dependencies: 'devel'
    _permission: 'access devel information'

Based on this route, a local task is defined as well.

Now, if Devel is disabled, the route is skipped during the rebuilding process, and it never reaches the database.
The local task is also not shown.
However, trying to access another local task on the same level (store "Edit", for example) causes a crash:

Symfony\Component\Routing\Exception\RouteNotFoundException: Route "commerce.store_type_devel" does not exist. in Drupal\Core\Routing\RouteProvider->getRouteByName() (line 150 of core/lib/Drupal/Core/Routing/RouteProvider.php).
Drupal\Core\Routing\RouteProvider->getRouteByName(commerce.store_type_devel)
Drupal\Core\Menu\LocalTaskDefault->getRouteParameters(Symfony\Component\HttpFoundation\Request)
Drupal\Core\Menu\LocalTaskManager->getTasksBuild(commerce.store_type_edit)

Proposed resolution

The LocalTaskManager should skip checking local tasks based on missing routes.

CommentFileSizeAuthor
#76 interdiff_72-76.txt11.24 KBravi.shankar
#76 2315801-76.patch24.61 KBravi.shankar
#72 2315801-72.patch24.92 KBDieterHolvoet
#68 interdiff.txt9.47 KBjurgenhaas
#68 2315801-68.patch25.03 KBjurgenhaas
#66 interdiff.txt14.09 KBjurgenhaas
#66 2315801-66.patch25.33 KBjurgenhaas
#65 interdiff.txt15.58 KBjurgenhaas
#65 2315801-65.patch24.83 KBjurgenhaas
#53 2315801-53.patch25.43 KBpcambra
#53 interdiff.txt511 bytespcambra
#51 2315801-51.patch25.43 KBpcambra
#51 interdiff.txt775 bytespcambra
#47 2315801-47.patch24.68 KBpcambra
#47 interdiff.txt13.03 KBpcambra
#44 2315801-44.patch14.89 KBpcambra
#44 interdiff.txt905 bytespcambra
#39 2315801-39.patch14.81 KBpcambra
#39 interdiff.txt4.45 KBpcambra
#36 2315801-36.patch14.15 KBpcambra
#36 interdiff.txt4.7 KBpcambra
#35 interdiff.txt6.57 KBpcambra
#34 2315801-34.patch16.14 KBpcambra
#34 interdiff.txt16.14 KBpcambra
#29 2315801.patch11.62 KBRalt
#23 interdiff.txt2.69 KBdawehner
#23 2315801-23.patch11.59 KBdawehner
#20 interdiff.txt3.97 KBpcambra
#20 2315801-20.patch11.61 KBpcambra
#17 interdiff.txt565 bytesdawehner
#17 2315801-17.patch7.64 KBdawehner
#15 interdiff.txt8.85 KBdawehner
#15 module_dependencies-2315801-10.patch7.77 KBdawehner
#13 2315801-13-local-task-missing-route-crash.patch7.67 KBmglaman
#7 2315801-7-local-task-missing-route-crash.patch8 KBbojanz
#6 2315801-6-local-task-missing-route-crash.patch7.06 KBbojanz
#5 2315801-5-local-task-missing-route-crash.patch0 bytesbojanz
#1 2315801-1-local-task-missing-route-crash.patch984 bytesbojanz
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

bojanz’s picture

Title: Generating a local task based on a missing route name causes a crash » Generating a local task based on a missing route causes a crash
Status: Active » Needs review
FileSize
984 bytes

Here's a patch with a proposed fix. The comment could be improved.
Needs a test, of course.

Something similar might be desirable for menu links as well, dblog needs to implement dblog_menu_links_discovered_alter() because one of its routes
depends on the search module. The situation is less urgent there because the alter hook provides an alternative, unlike with local tasks (where implementing hook_menu_local_tasks_alter() doesn't behave correctly).

bojanz’s picture

Issue summary: View changes
bojanz’s picture

Issue summary: View changes
bojanz’s picture

I've spoken to dawehner about introducing a dependency key on the menu link/task/action plugin definitions, and am working on that, but it makes sense to finish this patch as well, because I don't think the system should crash if the route is missing for any reason.

bojanz’s picture

And here's the alternative discussed with dawehner.

bojanz’s picture

Now with 100% more content.

bojanz’s picture

Moved the trait to the plugin namespace, reduced some boilerplate code (IRC review).

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Menu/MenuLinkManager.php
    @@ -182,7 +185,16 @@ public function getDefinitions() {
    +      if (isset($definition['module_dependencies'])) {
    +        if (($this->checkModuleDependencies($definition['module_dependencies'])) == FALSE) {
    +          unset($definitions[$plugin_id]);
    +        }
    +      }
         }
    +
    +    // Filter out definitions with unmet module dependencies.
    +    $this->filterByModuleDependencies($definitions);
    

    Do we still need both? I guess you just forgot to drop that code.

  2. +++ b/core/lib/Drupal/Core/Plugin/ModuleDependencyTrait.php
    @@ -0,0 +1,103 @@
    +  }
    +}
    

    Just put a newline between them.

The last submitted patch, 6: 2315801-6-local-task-missing-route-crash.patch, failed testing.

The last submitted patch, 5: 2315801-5-local-task-missing-route-crash.patch, failed testing.

jhodgdon’s picture

Version: 8.x-dev » 8.0.x-dev
dawehner’s picture

You could argue that 'dependencies][module' could be used as well, but this would imply that other dependencies are supported which is simply not the case. Let's be more parallel with routing instead.

mglaman’s picture

Reroll of #7 with updates from review in #8

Status: Needs review » Needs work

The last submitted patch, 13: 2315801-13-local-task-missing-route-crash.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
7.77 KB
8.85 KB

Worked a bit on this issue:

  • Removed the manual calls from the menu plugin managers and moved it into the default plugin manager
  • Added a proper test coverage
  • Some things here and there

Just a general idea: we could pull in the provider filtering as well.

Status: Needs review » Needs work

The last submitted patch, 15: module_dependencies-2315801-10.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
7.64 KB
565 bytes

I totally forgot that traits sucks.

jibran’s picture

I was thinking how can we create a test only patch for this bug?

dawehner’s picture

Well, it should be for sure possible to create a menu link in some new test module which depends on the existance of some other module.

* Check that the link is not there is the other module exists
* Enable the other module, check that the link appears.

pcambra’s picture

FileSize
11.61 KB
3.97 KB

I've tried to do the test that @dawehner mentions in #19 and create a new module with the route dependent on another module (menu_ui in this case), then enable the second module, but the link seems to be there for both cases, I'll ask for feedback.

dawehner’s picture

+++ b/core/modules/system/src/Tests/Menu/MenuLinkDependencyTest.php
@@ -0,0 +1,41 @@
+    /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+    $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+    $definitions = $menu_link_manager->getDefinitions();
+    $this->assertTrue(empty($definitions['menu_dependency_test.dependency']), 'A menu link with unmet dependencies is not found.');

Maybe also check the error in the IS which is executing the local task manager?

Status: Needs review » Needs work

The last submitted patch, 20: 2315801-20.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
11.59 KB
2.69 KB

Maybe more like this? Open for discussion.

mglaman queued 23: 2315801-23.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 23: 2315801-23.patch, failed testing.

jibran’s picture

+++ b/core/modules/system/tests/modules/menu_dependency_test/menu_dependency_test.links.menu.yml
@@ -1,3 +1,4 @@
+  module_dependencies: 'menu_ui'

+++ b/core/modules/system/tests/modules/menu_dependency_test/menu_dependency_test.routing.yml
@@ -4,5 +4,3 @@ menu_dependency_test.menu_dependency_test:
-  requirements:
-    _module_dependencies: 'menu_ui'

I am not sure about this change but other then this it is RTBC.

bojanz queued 23: 2315801-23.patch for re-testing.

The last submitted patch, 23: 2315801-23.patch, failed testing.

Ralt’s picture

FileSize
11.62 KB

Rerolled #23 to have a non-conflicting class name.

bojanz’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 29: 2315801.patch, failed testing.

dawehner’s picture

#26 should be adressed afaik ... if possible it makes sense to define it both routing level ... if you don't control the route, maybe define it on the menu link level as well.

dawehner’s picture

As talked with @pcambra: It would be cool if the property set on the route would be automatically inherited/reflected for the menu links as well.

pcambra’s picture

Edit: This is not the patch you're looking for.

pcambra’s picture

FileSize
6.57 KB

Generated an interdiff with patchutils, it should be easier to review.

pcambra’s picture

FileSize
4.7 KB
14.15 KB

Ok, so I had added an extra file totally unrelated, sorry about that. Here's the right patch with interdiff and all.

As discussed with @dawehner, adding the check on the routes as well as on the menu links, so if you add module_dependencies to either part, it would work.
Sorry for the horrible code in that part, but I'm not really sure how to get the routes in a clean way (I tried to inject the service but there's already a constructor in this part). Directions on how to improve that part are highly appreciated.

Also, I've noticed a mismatch when using YamlDiscovery, some bits like RouteBuilder use the component directly whereas other parts use the plugin. Is that expected?

dawehner’s picture

+++ b/core/lib/Drupal/Core/Menu/MenuLinkManager.php
@@ -190,6 +190,25 @@
+    // Also filter by the unmet route dependencies.
+    $routeDiscovery = new YamlDiscovery('routing', $this->moduleHandler->getModuleDirectories());
+    $route_definitions = $routeDiscovery->getDefinitions();
+    foreach ($definitions as $plugin_id => $plugin_definition) {
+      $route_name = $plugin_definition['route_name'];
+      if (!empty($route_definitions[$route_name]) && !empty($route_definitions[$route_name]['requirements']) && !empty($route_definitions[$route_name]['requirements']['_module_dependencies'])) {
+        if ($modules = $route_definitions[$route_name]['requirements']['_module_dependencies']) {
+          $explode_and = $this->explodeString($modules, '+');
+          foreach ($explode_and as $module) {
+            // If any moduleExists() call returns FALSE, remove the menu and
+            // move on to the next.
+            if (!$this->moduleHandler->moduleExists($module)) {
+              unset($definitions[$plugin_id]);
+            }
+          }
+        }
+      }
+    }

so a) we do we not just check for 'route_name' of the plugin definition in filterByModuleDependencies to copy it over? (Note: here you just use it for menu links, but don't you want to have the exact same behaviour for local tasks / actions ...

Also, I've noticed a mismatch when using YamlDiscovery, some bits like RouteBuilder use the component directly whereas other parts use the plugin. Is that expected?

I don't see a problem with that :) Stuff which aren't plugins (routes) don't use the YamlDiscovery class from plugins, stuff which are plugins use the YamlDiscovery class
from plugins, which itself uses the other yaml discovery internally.

dawehner’s picture

Status: Needs review » Needs work

.

pcambra’s picture

Status: Needs work » Needs review
FileSize
4.45 KB
14.81 KB

Here's a second try applying #37, I have add an additional check instead of merging the dependencies because it would be very hard to combine something like ['a','b','c'] with 'd','e' or 'f'+'g'

Status: Needs review » Needs work

The last submitted patch, 39: 2315801-39.patch, failed testing.

Status: Needs work » Needs review

pcambra queued 39: 2315801-39.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 39: 2315801-39.patch, failed testing.

dawehner’s picture

+++ b/core/lib/Drupal/Core/Plugin/ModuleDependencyTrait.php
@@ -21,12 +23,40 @@
+   */
+  protected function getRouteDependencies(array $definition, array $route_definitions) {
+    if (isset($definition['provider']) && isset($route_definitions[$definition['provider']])) {
+      return NestedArray::getValue($route_definitions, array($definition['provider'], $definition['route_name'],'requirements', '_module_dependencies'));
     }

Good question whether we have to merge, I would be fine with just overriding the value from the parent, if defined ... What do you think about it?

pcambra’s picture

Status: Needs work » Needs review
FileSize
905 bytes
14.89 KB

Here's another take, I think I had the wrong check.

just overriding the value from the parent

What if there's a mismatch? if the plugin depends on the module but the route doesn't, isn't this forcing to have it defined in both places?

Status: Needs review » Needs work

The last submitted patch, 44: 2315801-44.patch, failed testing.

dawehner’s picture

What if there's a mismatch? if the plugin depends on the module but the route doesn't, isn't this forcing to have it defined in both places?

How can the menu link depend on it, but not the route? Maybe its just an example which is not obvious for me.

pcambra’s picture

Status: Needs work » Needs review
FileSize
13.03 KB
24.68 KB

What about this then?

I've had to add unit test expectations about the getModuleDirectories method of the module handler that is mocked as the filterByModuleDependencies method is called from the DefaultPluginManager as well.

Status: Needs review » Needs work

The last submitted patch, 47: 2315801-47.patch, failed testing.

dawehner’s picture

+++ b/core/lib/Drupal/Core/Plugin/ModuleDependencyTrait.php
@@ -55,7 +55,6 @@ protected function filterByModuleDependencies(array &$definitions) {
-//    if (isset($definition['provider']) && isset($route_definitions[$definition['provider']])) {

what is with that?

pcambra’s picture

It was a commented line I leftover in #44, still dealing with some odd test failures

pcambra’s picture

Status: Needs work » Needs review
FileSize
775 bytes
25.43 KB

Missed one of the manager tests.

jibran’s picture

Status: Needs review » Reviewed & tested by the community

Let's fix this.

+++ b/core/modules/system/src/Tests/Menu/MenuLinkDependencyTest.php
@@ -0,0 +1,56 @@
+ * Definition of Drupal\system\Tests\Menu\MenuLinkDependencyTest.

Contains

pcambra’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
511 bytes
25.43 KB

Here we go.

bojanz’s picture

Status: Needs review » Reviewed & tested by the community

And back.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
@@ -27,6 +27,7 @@
+  use ModuleDependencyTrait;

+++ b/core/lib/Drupal/Core/Plugin/ModuleDependencyTrait.php
@@ -0,0 +1,150 @@
+    $route_definitions = $this->getRouteDefinitions();
+
+    foreach ($definitions as $plugin_id => &$definition) {
+      // If the route has module dependencies, use them instead, menu links,
+      // actions, etc shouldn't have dependencies that are not shared with the
+      // routes.
+      if ($route_dependencies = $this->getRouteDependencies($definition, $route_definitions)) {
+        $definition['module_dependencies'] = $route_dependencies;
+      }
...
+  protected function getRouteDependencies(array $definition, array $route_definitions) {
+    if (isset($definition['provider']) && isset($definition['route_name'])) {
+      return NestedArray::getValue($route_definitions, array($definition['provider'], $definition['route_name'],'requirements', '_module_dependencies'));
+    }
+  }

The trait is being used in the DefaultPluginManager which is for all plugins but contains very specific assumptions that it is dealing with routes.

bojanz’s picture

As we said on IRC, the naming and usage of the trait is weird now that it automatically uses the route's dependencies (which is definitely something we want to do).

So, we want to remove the trait from the DefaultPluginManager, ensure that the menu link, local action, local task managers use it.
We also need to rename it, perhaps to RouteModuleDependency? "Provides a trait for checking the module dependencies of a plugin's route." ?

EDIT: I was slower than Alex on the "needs work".

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

jurgenhaas’s picture

FileSize
24.83 KB
15.58 KB

Re-rolled the patch for 8.9.x-dev in order to continue working on this.

Only problem has been that I couldn't apply the last test any more:

diff --git a/core/tests/Drupal/Tests/Core/Render/ElementInfoManagerTest.php b/core/tests/Drupal/Tests/Core/Render/ElementInfoManagerTest.php
index ef9a956..01cd07d 100644
--- a/core/tests/Drupal/Tests/Core/Render/ElementInfoManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/ElementInfoManagerTest.php
@@ -68,6 +68,9 @@ public function testGetInfo($type, $expected_info, $element_info, callable $alte
       ->will($this->returnCallback($alter_callback ?: function($info) {
         return $info;
       }));
+    $this->moduleHandler->expects($this->once())
+      ->method('getModuleDirectories')
+      ->willReturn(array());
 
     $this->cache->expects($this->at(0))
       ->method('get')

There is no reference in the new code which would allow me to re-apply this change. Maybe somebody could help me with this?

I'm planning to work on comments from #55 next.

jurgenhaas’s picture

Status: Needs work » Needs review
FileSize
25.33 KB
14.09 KB

I've now moved the ModuleDependencyTrait out of the default plugin manager over to the route related plugin managers in the Drupal\Core\Menu namespace.

ravi.shankar’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

Patch #66 didn't apply.

jurgenhaas’s picture

Status: Needs work » Needs review
FileSize
25.03 KB
9.47 KB

Sorry for that, for some reason my patch was created with a duplicated base path for each file. Not sure how that happened but it's fixed now.

Status: Needs review » Needs work

The last submitted patch, 68: 2315801-68.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

ravi.shankar’s picture

Issue tags: -Needs reroll

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

DieterHolvoet’s picture

I re-rolled the patch for 9.4.x.

DieterHolvoet’s picture

I encountered this issue using the Scheduler module: it adds a local task pointing to a module-provided view. When I disable this view, the site crashes because the route no longer exists. The current patch doesn't fix the issue, because Views doesn't add module_dependencies to its routes.

DieterHolvoet’s picture

Version: 9.2.x-dev » 9.3.x-dev
ravi.shankar’s picture

Fixed Drupal CS issues of patch #72.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.