Problem/Motivation

\Drupal\Core\Menu\LocalTaskManager::getLocalTasksForRoute is not optimal at the moment

Proposed resolution

Improve it by extracting things into dedicated methods which have each proper documentation. This itself is worth it

Remaining tasks

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#61 interdiff_59-61.txt926 bytesRatan Priya
#61 2653144-61.patch15.85 KBRatan Priya
#59 2653144-59.patch15.85 KBsmustgrave
#59 interdiff-56-59.txt1.75 KBsmustgrave
#56 2653144-56.patch15.8 KBsmustgrave
#56 2653144-56-tests-only.patch3.83 KBsmustgrave
#56 interdiff-53-56.txt2.4 KBsmustgrave
#53 2653144-53.patch15.76 KBsmustgrave
#53 interdiff-48-53.txt4.28 KBsmustgrave
#50 2653144-50.patch16.27 KBAnkit.Gupta
#49 2653144-49.patch16.27 KBAnkit.Gupta
#48 reroll_diff_35-48.txt1.47 KBpooja saraah
#48 2653144-48.patch16.27 KBpooja saraah
#46 Screenshot 2022-08-01 at 12.54.00 PM.png208.34 KBakshay_d
#35 interdiff-2653144-32-35.txt1.88 KByogeshmpawar
#35 2653144-35.patch16.27 KByogeshmpawar
#32 2653144-32.patch16.24 KBgrathbone
#27 interdiff-2653144-22-27.txt10.05 KBgrathbone
#27 2653144-27.patch16.23 KBgrathbone
#24 LocalTaskManagerActiveTrail.diff16.62 KBgrathbone
#22 LocalTaskManager.diff15.24 KBgrathbone
#20 LocalTaskManager.diff15.05 KBgrathbone
#18 LocalTaskManager.diff14.74 KBgrathbone
#14 LocalTaskManager.diff10.03 KBgrathbone
#11 LocalTaskManager.diff10.01 KBgrathbone
#4 interdiff.txt3.31 KBdawehner
#4 2653144-4.patch11.09 KBdawehner
#2 2653144-2.patch10.1 KBdawehner

Issue fork drupal-2653144

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner created an issue. See original summary.

dawehner’s picture

Status: Active » Needs review
FileSize
10.1 KB

There we go.

Crell’s picture

dawehner++

  1. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -211,79 +211,15 @@ public function getLocalTasksForRoute($route_name) {
    +        $this->setLocalTasksCache($route_name, $base_routes, $parents, $children);
    

    I would structure this differently. Rather, we want a getLocalTasks() method, which internally is just a caching/memoizing wrapper around a generateLocalTasks() method (or similar). Then just use the get method when we actually need it, and the caching all happens behind the scenes.

  2. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -415,4 +351,141 @@ protected function isRouteActive($current_route_name, $route_name, $route_parame
    +    foreach ($definitions as $plugin_id => $task_info) {
    +      if (!empty($base_routes[$task_info['base_route']]) && (empty($task_info['parent_id']) || !empty($parents[$task_info['parent_id']]))) {
    +        // Concat '> ' with root ID for the parent of top-level tabs.
    +        $parent = empty($task_info['parent_id']) ? '> ' . $task_info['base_route'] : $task_info['parent_id'];
    +        $children[$parent][$plugin_id] = $task_info;
    +      }
    

    This would be clearer as an array_filter call, followed by an array_map. (Mainly because that encourages breaking off defined, named methods for what the filter criteria are and what we're actually doing with it.)

  3. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -415,4 +351,141 @@ protected function isRouteActive($current_route_name, $route_name, $route_parame
    +        // Populate the definitions we use in the next loop. Using a
    +        // reference like &$task_info causes bugs.
    +        $definitions[$plugin_id]['base_route'] = $definitions[$task_info['parent_id']]['base_route'];
    

    I do not fully understand the purpose of this code, but the comment is enough to make me curl up in a ball in fear. Modify information for the next foreach iteration? Er... that sounds highly scary.

dawehner’s picture

This would be clearer as an array_filter call, followed by an array_map. (Mainly because that encourages breaking off defined, named methods for what the filter criteria are and what we're actually doing with it.)

Well, you would need an array_walk() if you actually have to implement it, see + $children[$parent][$plugin_id] = $task_info;

I do not fully understand the purpose of this code, but the comment is enough to make me curl up in a ball in fear. Modify information for the next foreach iteration? Er... that sounds highly scary.

Well, its how you can iterate over trees without function calls.

I would structure this differently. Rather, we want a getLocalTasks() method, which internally is just a caching/memoizing wrapper around a generateLocalTasks() method (or similar). Then just use the get method when we actually need it, and the caching all happens behind the scenes.

Maybe something like that?

Status: Needs review » Needs work

The last submitted patch, 4: 2653144-4.patch, failed testing.

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

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now 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.

pwolanin’s picture

Internal re-org seems fine, if you have the energy

dawehner’s picture

Well yeah, I think its doable and well, the current code in HEAD is indeed hard to grock.

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

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now 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.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now 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.

grathbone’s picture

Refactored the existing code. In the process, I took care of the @todo to allow for more than 2 levels of routes.

In explanation

  • I used the findDefinitions to generate children before the definitions were cached but after definitions were able to be altered – this allows the children definitions to always have up-to-date info.
  • I then used a task to find the current route's breadcrumb path.
  • Then built the current route's task tree around the breadcrumb, unsetting children on routes that were unneeded.

Benefits of refactoring, using tree, and caching the children:

  • Sets and allows local tasks that have more than 2 levels.
  • Allows the two top levels to always have correct active_paths, regardless of if more than 2 levels are rendered
  • Caches a data-intensive function that finds children, which saves time for every route look up
  • Cleans up parent-child tree look-up, breaking it into discernible tasks.

Two questions though:

  1. Should we still set the $parents, $children, and $base_routes info in the cached data array and merely mark them as deprecated to be removed in 9? They're now unneeded in in this class, but they'd be easy to generate using array_keys and a maybe a foreach loop.
  2. Do we need a generateTaskTreeData to call inside of the getTaskTreeData?

Let me know what you guys think.

grathbone’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 11: LocalTaskManager.diff, failed testing.

grathbone’s picture

Re-rolling the patch. Updated the documentation. Please see https://www.drupal.org/node/2653144#comment-12084135 for original comment.

grathbone’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 14: LocalTaskManager.diff, failed testing.

dawehner’s picture

@grathbone I'm highly confused. This is about the LocalTaskManager, but you refactor something about breadcrumbs. Is there some misunderstanding on my side what is going on?

grathbone’s picture

Status: Needs work » Needs review
FileSize
14.74 KB

@dawehner That's my fault for reusing terminology. The thought is that it checks the route that is passed in, and parses through the plugin definitions for that route's parent plugin definition IDs (breadcrumbs). This way, it can use the parent plugin IDs (breadcrumbs) to then move down the local task menu tier by tier to allow more than first and secondary menus (to take care of the @todo that has been in there for a while.) This additionally lets modules to continue to show active path in first and secondary menus without necessarily having the current page on a visible local task.

In doing so, I attempted to also refactor the class to break out certain methods and make it more readable.

Here is a new patch. Note: I undid the part about caching children and made some updates since the last patch.

Status: Needs review » Needs work

The last submitted patch, 18: LocalTaskManager.diff, failed testing.

grathbone’s picture

Status: Needs work » Needs review
FileSize
15.05 KB

These tests aren't failing locally, so here's another shot in the dark. I'm currently trying to determine why Drupal\Tests\system\Functional\Menu\MenuAccessTest erred, since the message response is accurate (that the link exists).

Status: Needs review » Needs work

The last submitted patch, 20: LocalTaskManager.diff, failed testing.

grathbone’s picture

Status: Needs work » Needs review
FileSize
15.24 KB

So this patch addresses two issues: refactoring the code class to make it easier to manage, and taking care of the @todo to allow more than two levels of depth.

Changes

It set up 3 new methods on the LocalTaskManager test:

  • getBreadcrumbsForRoute
  • generateChildrenForDefinitions
  • getTaskTreeData

I modified one test, LocalTaskManagerTest, to change the test cached response.

Possible changes

@dawehner, I could change getBreadCrumbsForRoute to getActiveTrailForRoute.

Also, I considered setting up a method getTreeByActiveTrail to create the task tree from passed-in $definitions and $activeTrail, to make this class more reusable, and then removing generateChildrenForDefinitions.

dawehner’s picture

@dawehner, I could change getBreadCrumbsForRoute to getActiveTrailForRoute.

That sounds like a better name, given that breadcrumb is already a word in the domain of menus.

One question: Is there reason this method is public?

grathbone’s picture

Modified a bit and renamed to active trail.

@dawehner My thought is so that these methods could be used elsewhere, I.E. if someone would want use the manager in their own module/block/alter to load different definitions, modify the active trail, or use a full menu-tree setup (nested unordered lists) compared to the multi-level (first, secondary) setup that local tasks currently uses by default.

dawehner’s picture

@grathbone
Sure, but well, there is something like a BC break if we add new methods to an existing interface. This is not for free, and the discussions with the committers can take time. We can always iterate in another issue and make it public, but to avoid friction its better to keep it as it is, for now.

Please also keep in mind to post interdiffs as it makes it easier, at least for me, to follow along what you change.

Status: Needs review » Needs work

The last submitted patch, 24: LocalTaskManagerActiveTrail.diff, failed testing.

grathbone’s picture

@dawehner Oh, alright. I modified the methods and made them protected and removed them from the interface class. Also, sorry about that; I'm new to contributing and I'm still getting the hang of testing and creating patches. Thanks for sending over the interdiff link!

I made an interdiff between the last patch that passed (#22) and the new one.

grathbone’s picture

Status: Needs work » Needs review
dawehner’s picture

lso, sorry about that; I'm new to contributing and I'm still getting the hang of testing and creating patches. Thanks for sending over the interdiff link!

Hey, no worries. I'm more than happy you try to tackle this bit of tricky code. I'm just a modest helper for you.

@grathbone
One next great step someone could do is to update the issue summary to describe what kind of refactorings got applied. This helps reviewers to know exactly what is going on.

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

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now 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.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now 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.

grathbone’s picture

borisson_’s picture

Status: Needs review » Needs work

There is a couple of coding standards issues in the patch.

core/lib/Drupal/Core/Menu/LocalTaskManager.php
line 220	Expected 1 space after WHILE keyword; 0 found
228	   Expected newline after closing brace
254	   Expected 1 space after IF keyword; 0 found

Those should be fixed, I'd love otherwise RTBC this patch but I don't feel sufficiently sure about this, it looks like this breaks the current behavior, because the test also needed to be changed.

Back to needs work for those coding standards.

yogeshmpawar’s picture

Assigned: Unassigned » yogeshmpawar
yogeshmpawar’s picture

Assigned: yogeshmpawar » Unassigned
Status: Needs work » Needs review
FileSize
16.27 KB
1.88 KB

Coding standard issues are solved in updated patch mentioned in #33 & also added an interdiff.

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

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now 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.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.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.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). 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.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now 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.

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

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

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

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.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.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.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.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now 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.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll
akshay_d’s picture

Assigned: Unassigned » akshay_d

Working on this issue

akshay_d’s picture

Assigned: akshay_d » Unassigned
Status: Needs work » Needs review
FileSize
208.34 KB

#35 apply cleanly to 9.5.x.
Added the screenshot after applying please review and let us if anything else needs to be updated.

Thanks

borisson_’s picture

Thank you for reviewing this issue @akshay_d

The automated testing infrastructure tells us whether the patch applies, so we do not need people to review that, and also not needed to post a screenshot for that.

What we do need people to review is whether the issue has a correct scope, whether it passes the core gates, whether the solution completely fixes the problem without introducing other problems, and whether it's the best solution we can come up with. See the patch review guide for more information.

When you do post a review, be sure to describe what you reviewed and how, this helps other reviewers.

pooja saraah’s picture

Attached Reroll patch against Drupal 9.5.x

Ankit.Gupta’s picture

Rerolled patch #35 with Drupal 9.5.x

Ankit.Gupta’s picture

Rerolled patch #49 with Drupal 9.5.x

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

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now 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.

joachim’s picture

Status: Needs review » Needs work

This looks like a useful thing to do -- smaller and simpler methods are easier to understand, debug, and maintain!

However, the docs aren't clear in places.

I'm also concerned that the tests are being changed in this patch. If this is just a refactoring, then tests should not change -- instead, they should remain the same to show us they still pass and so the functionality is unchanged by the refactoring!

  1. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -191,94 +191,184 @@ public function getDefinitions() {
    +   * Return active-trail of task definition plugin ids for the current route.
    

    This would be a great place to explain what the active-trail means.

    Also, if there is a $route_name parameter, then it's not the 'current' route, it's the 'given' route.

  2. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -191,94 +191,184 @@ public function getDefinitions() {
    +   *   The plugin definitions list.
    

    This needs to say the key is expected.

  3. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -191,94 +191,184 @@ public function getDefinitions() {
    +   *   The modified plugin definitions list with children sub-arrays.
    

    This needs more detail.

  4. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -191,94 +191,184 @@ public function getDefinitions() {
    +   * Build a nested tree of plugin definitions based on the active_trail.
    

    Should be 'Builds'. Same for all the other methods.

    What's 'active_trail'?

  5. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -191,94 +191,184 @@ public function getDefinitions() {
    +   * @return array
    

    Missing blank line.

  6. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -191,94 +191,184 @@ public function getDefinitions() {
    +   * Return an array containing tree and breadcrumb data for the current route.
    

    Same - given route, not current route.

smustgrave’s picture

@Ankit.Gupta and @pooja saraah
You can check for build errors make sure to run ./core/scripts/dev/commit-code-check.sh before uploading a patch to make sure there are no issues with code formatting. see https://www.drupal.org/docs/develop/development-tools/running-core-devel...

@Ankit.Gupta
Please upload an interdiff so we can see the changes being made between patches.

Fixed up the patch. Attempted to address the points in #52 but sure it will need tweaking. Tech writing is not my strongest.

catch’s picture

Just nits:

  1. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -191,94 +191,186 @@ public function getDefinitions() {
     
    +  /**
    +   * Return active-trail (specific task definition plugins) for the given route.
    +   *
    

    I don't know what (specific task definition plugins) means - I think this should move under the summary with a bit more detail.

  2. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -191,94 +191,186 @@ public function getDefinitions() {
    +      // Create children array on definition for uniformity.
    +      if (!array_key_exists('children', $definition)) {
    +        $definition['children'] = [];
    

    array_key_exists() should be fully qualified (\array_key_exists()) for performance - PHP has an opcode implementation.

  3. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -191,94 +191,186 @@ public function getDefinitions() {
    +        $definition['children'] = [];
    +      }
    +      // Add definition pointer to parent's child array (if not its own parent).
    +      if (isset($definitions[$parent_id]) && $parent_id !== $id) {
    

    We could reverse the sentence to remove the parentheses:

    If the definition isn't the parent of itself, add a definition pointer to the parent's child array.

  1. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -191,94 +191,186 @@ public function getDefinitions() {
    +      $task_id = end($definition_ids);
    

    I think this should be 'upwards'? Not entirely sure if that's a British vs. Americanism though.

  2. +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
    @@ -191,94 +191,186 @@ public function getDefinitions() {
    +    $active_top_level = current($active_trail);
    +    $base_route = ($active_top_level && array_key_exists($active_top_level, $definitions)) ? $definitions[$active_top_level]['base_route'] : NULL;
    +    // Unset if both not top-level active-trail and one of the following:
    

    Same thing with array_key_exists()

One more question - there's some test coverage changes, is that definitely all additions, or is it changing the expectations of the test. Would be good to have a 'patch only patch' to see whether it doesn't break the existing test coverage without those changes.

catch’s picture

Status: Needs review » Needs work
smustgrave’s picture

Status: Needs work » Needs review
FileSize
2.4 KB
3.83 KB
15.8 KB

#54
1 = Updated with a more clear version from a previous patch
2 = updated both instances
3 = Updated
4 = not sure I follow
5 = Updated

Also uploading test-only patch.

The last submitted patch, 56: 2653144-56-tests-only.patch, failed testing. View results

larowlan’s picture

Drive by review, but we should be able to add type-hints to the new functions - both for the params and the return types

smustgrave’s picture

Updated with typehints.

BramDriesen’s picture

+++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
@@ -191,94 +191,187 @@ public function getDefinitions() {
+      // Start ancestors array with last-available non-NULL active task id
+      // gathered from route and work upward.
+      $task_id = end($definition_ids);

RE #56 4. was about the word upward in the comment here. Which needs to be changed to upwards I guess.

Ratan Priya’s picture

@BramDriesen,
Made changes according to #60.
Needs review.

BramDriesen’s picture

Status: Needs review » Needs work

See error below for the failed test run.

FILE: /var/www/html/core/lib/Drupal/Core/Menu/LocalTaskManager.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 267 | ERROR | Doc comment short description must be on a single
     |       | line, further text should be a separate paragraph
     |       | (Drupal.Commenting.DocComment.ShortSingleLine)
----------------------------------------------------------------------

Time: 10.19 secs; Memory: 6MB


PHPCS: failed

Version: 10.1.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, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bharath-kondeti made their first commit to this issue’s fork.

bharath-kondeti’s picture

Addressed #62 and Raised an MR. Please review.

bharath-kondeti’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community
BramDriesen’s picture

Status: Reviewed & tested by the community » Needs work