related to #1908756: Separate Action Links (MENU_LOCAL_ACTION) from hook_menu()

For tabs we need to have hierarchy which distinguishes them from local actions.

A knotty problem to be solved is also the _to_arg functionality which is used in tracker module and other places. The pattern for solving this would probably apply to both tabs and local actions. Basically we need to be able to call a function to populate a value when constructing a synthetic path to make a request object.

Follow ups:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett’s picture

Issue tags: +MenuSystemRevamp, +WSCCI

Tagging

pwolanin’s picture

following on from the work in #1981644: Figure out how to deal with 'title/title callback', it would make snese to switch to plugins rather than a hook.

pwolanin’s picture

Status: Active » Needs work
FileSize
14.87 KB

This is a starting patch - doesn't actually sort the hierarchy yet, etc. Just to show the direction.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
22.27 KB

Ok, this is generally working right - posting so we can get a test run.

pwolanin’s picture

FileSize
11.81 KB
31.38 KB

Includes fixes for the active class on the tab LI and added tests from dawehner

Status: Needs review » Needs work

The last submitted patch, 2004334-5.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
9.17 KB
32.95 KB

With more tests/fixes from dawehner

Status: Needs review » Needs work

The last submitted patch, 2004334-7.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
1.62 KB
33.04 KB

Simple fix for test - set a weight so the tabs are in the expected order.

Status: Needs review » Needs work

The last submitted patch, 2004334-9.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
850 bytes
33.06 KB

Ah, same problem - we need to be explicit about weight for this tab.

sdboyer’s picture

generally speaking, this looks sane. i'm still a touch weirded out by the use of plugins for this, but that's ok.

the thing that's keeping me from RTBCing this is how the logic has been split. MenuLocalTaskManager::getTasksBuild() is the only public method that's ever actually called on this thing, and it seems rather singly-focused on doing the exact same thing as the rest of the logic in menu_local_tasks(). if there isn't another use case for calling that method, then i think that logic should be moved back in to menu_local_tasks(), at which point the other public methods on MenuLocalTaskManager will suddenly have a reason to be public. otherwise, we're effectively just dividing one logical task into two separate places - and to find the second place from menu_local_tasks(), you have to check system.services.yml to find the class that actually comes back from plugin.manager.system.menu_local_task.

from what i can tell, the only thing that would really need to change in moving it back out to the function is asking the container for the route provider directly. if that's the case, we can hit \Drupal for that.

i imagine that your response to this will be, "we have it this way so people can swap the logic," and/or "we're intending to get rid of the rest of menu_local_tasks() and have it just be this." if that's the case, i could probably be convinced to RTBC as-is, but for the former i'd like more details on whether or not such a use case is realistic, and for the latter i'd like to see a followup issue for that removal :)

pwolanin’s picture

Status: Needs review » Needs work

per Tim, needs work - need to restore and fake up hook_menu entries so breadcrumbs don't break until they are really fixed.

pwolanin’s picture

@sdboyer - yes, basically all the rest of the code in menu_local_tasks() dealing with tabs and actions should go away.

dawehner’s picture

FileSize
40.09 KB
9.54 KB

Add a first basic unit test for the local task manager.

dawehner’s picture

Added some of the followups to the summary.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
11.68 KB
40.88 KB

breadcrumb fixes by adding back minimal hook_menu entries, added comments, and added unit test from dawahner

pwolanin’s picture

Issue summary: View changes

Added follow ups

sdboyer’s picture

Status: Needs review » Reviewed & tested by the community

@pwolanin added comments and opened a followup for removing the rest of menu_local_tasks() at #2032587: Clear menu_router code out of function menu_local_tasks() when all actions and tabs are converted to plugins. that meets my criteria. tim can knock it back if he's not OK :)

tim.plunkett’s picture

RTBC +1, this works for me.

YesCT’s picture

Issue tags: +RTBC July 1

This issue was RTBC and passing tests on July 1, the beginning of API freeze.

fubhy’s picture

FileSize
41.27 KB
8.46 KB

Fixing some minors... Also, same thing as over at #2031473: Convert menu local actions to plugins so that we can generate dynamic titles and paths: We should not put the controller resolver resolved methods in the interface.

Also, setters should always return $this imho unless they can return something else that makes sense. The rest are just name changes, 180 character limit fixes and fixing copy&paste issues (local actions/local tasks).

dawehner’s picture

Personally I prefer the interface one, as we do the same with for form interface as well and an interface is a nicer documentation then an exception but I won't block that.

fubhy’s picture

Personally I prefer the interface one, as we do the same with for form interface as well and an interface is a nicer documentation then an exception but I won't block that.

Forms are weird and special because of FormAPI and the form state/form array we got there. I'd prefer properly calling the form builder methods there at some point too though. But currently, as you will see by looking at HtmlFormController we do a lot of weird things with $form and $form_state and the request at that point. We may be able to fix that later. I hope so at least.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 2004334-21.patch, failed testing.

tim.plunkett’s picture

No no no no no.

I pushed back on the other issue about exceptions vs interfaces, lets not have this debate again. We agreed to use interfaces, and the patch was green and RTBC.

fubhy’s picture

Status: Needs work » Needs review
FileSize
3.78 KB
42.35 KB

The unit tests were stile trying to invoke the getTitle() / getPath() method on the interface mocks which don't have that anymore. Mocking the base class instead and testing for \BadMethodException when the method is missing.

dawehner’s picture

Issue tags: +PHPUnit

These unit tests addition looks great!

fubhy’s picture

Daniel, calm down. Every time you see a new unit test you completely freak out :D.

dawehner++

tim.plunkett’s picture

I withdraw my RTBC. This goes against the consensus we had.

fubhy’s picture

Okay, we agreed on IRC that we will do the interface/no interface thing in a follow-up. Daniel is going to come back with a re-roll (I gotta run in a few, office closes).

dawehner’s picture

FileSize
3.76 KB
42.18 KB

This adds #26 + #21 without the code changes.

dawehner’s picture

Status: Needs review » Needs work
Issue tags: -PHPUnit, -MenuSystemRevamp, -WSCCI, -RTBC July 1

The last submitted patch, drupal-2004334-30.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review

#31: drupal-2004334-30.patch queued for re-testing.

Status: Needs review » Needs work
Issue tags: +PHPUnit, +MenuSystemRevamp, +WSCCI, +RTBC July 1

The last submitted patch, drupal-2004334-30.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
1.72 KB
41.23 KB

Damn.

fubhy’s picture

FileSize
2.68 KB
41.1 KB

Going back to mocking the interface instead of the base class now that we got the methods on the interface again.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

This changes looks great, but yeah they don't matter that much.

YesCT’s picture

wth just happened since #20? can you say in englishish?

tim.plunkett’s picture

Issue tags: -WSCCI, -RTBC July 1

Since #20? @fubhy rewrote the patch with a new approach, added better docs, and the we agreed to return to the old approach.
So it should be almost the same as #20, but with better docs.

tim.plunkett’s picture

Issue tags: +WSCCI, +RTBC July 1

Someday we'll fix that bug.

fubhy’s picture

Issue tags: -WSCCI, -RTBC July 1

wth just happened since #20? can you say in englishish?

We do not have the perfect solution for the getTitle/getPath methods yet and neither the patch from #17 nor the patch from #21 should be considered a final solution. It's not an easy problem for which we don't have a solution yet, so Daniel opened a follow-up to discuss that and we agreed to revert back to #17 as that's what we previously had consensus about.

In short, the question is: How do we 'inject' contexts into plugins. The problem basically is that due to these dynamic contexts the methods signature would be different for each plugin (if we assume that we want to forward the contexts as method arguments, which is what this patch does) which makes using an interface rather hard/weird because in order to match the interface all the additional, dynamic arguments of the method have to be declared as optional by setting a default value ($foo = NULL).

/**
 * Implements getTitle() from the interface but the interface does not have any arguments on the method.
 */
function getTitle($foo = NULL, $bar = NULL) {
  // Do something with $foo and $bar which, even though they are declared optional
  // (in order to comply with the interface) they are really not optional because we
  // need them for this method in any case.
}

Here is the follow-up: #2035105: Figure out how to inject abritrary arguments into controllers/local action/local tasks whatever else plugins

fubhy’s picture

Issue tags: +WSCCI, +RTBC July 1

c'mon d.o ...

tim.plunkett’s picture

Status: Reviewed & tested by the community » Needs work

This needs a reroll in the theme of #2031473-28: Convert menu local actions to plugins so that we can generate dynamic titles and paths, including the removal of ContainerFactoryPluginBase (#2033447: Remove obsolete ContainerFactoryPluginBase).
I'll try to get to this during lunch or this evening, but if someone wants to jump in before me, feel free.

dawehner’s picture

Assigned: Unassigned » dawehner

Assign to myself.

dawehner’s picture

Assigned: dawehner » Unassigned
Status: Needs work » Needs review
FileSize
41.25 KB
tim.plunkett’s picture

+++ b/core/core.services.ymlundefined
@@ -169,6 +169,9 @@ services:
+  plugin.manager.local_task:

In the other, we had plugin.manager.menu.local_action, with the extra .menu.

+++ b/core/includes/menu.incundefined
@@ -2117,6 +2121,16 @@ function menu_local_tasks($level = 0) {
+      // The default manager class is \Drupal\Core\Menu\LocalTaskManager.

Is this really needed? I can't think of another time we've done this.

+++ b/core/lib/Drupal/Core/Annotation/Menu/LocalTask.phpundefined
@@ -0,0 +1,70 @@
+   * @var array

@var array|null (optional)

+++ b/core/lib/Drupal/Core/Annotation/Menu/LocalTask.phpundefined
@@ -0,0 +1,70 @@
+  /**
+   * The weight of the tab.
+   *
+   * @var int|NULL
+   */
+  public $weight;

This could probably have a default of 0 just for consistency.

+++ b/core/lib/Drupal/Core/Annotation/Menu/LocalTask.phpundefined
@@ -0,0 +1,70 @@
+   * @var int|NULL

@var int|null (optional)

+++ b/core/lib/Drupal/Core/Annotation/Menu/LocalTask.phpundefined
@@ -0,0 +1,70 @@
+   * @var array

@var array (optional)

+++ b/core/lib/Drupal/Core/Menu/LocalTaskInterface.phpundefined
@@ -0,0 +1,78 @@
+  /**
+   * Returns the localized title to be shown for this tab.
+   *
+   * Subclasses may add optional arguments like NodeInterface $node = NULL that
+   * will be supplied by the ControllerResolver.
+   */
+  public function getTitle();
...
+  /**
+   * Returns an internal Drupal path to use when creating the link for the tab.
+   *
+   * Subclasses may add optional arguments like NodeInterface $node = NULL that
+   * will be supplied by the ControllerResolver.
+   */
+  public function getPath();

Missing @return

+++ b/core/lib/Drupal/Core/Menu/LocalTaskInterface.phpundefined
@@ -0,0 +1,78 @@
+   * @see l().

no . at the end of @see

dawehner’s picture

FileSize
3.26 KB
41.31 KB

Thank you for the review.

This could probably have a default of 0 just for consistency.

There is a logic in the localtaskBase which sets the weight to -10, if the tab is basically the default tab. This is only applied if this value was not specified (null), but is not triggered when 0 is specified.

Status: Needs review » Needs work
Issue tags: -PHPUnit, -MenuSystemRevamp, -WSCCI, -RTBC July 1

The last submitted patch, local-task-2004334-48.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
Issue tags: +PHPUnit, +MenuSystemRevamp, +WSCCI, +RTBC July 1

#48: local-task-2004334-48.patch queued for re-testing.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

Thanks, the -10 part is a perfect explanation :)

Dries’s picture

This patch does not seem to apply. Asking for a re-test.

Dries’s picture

#48: local-task-2004334-48.patch queued for re-testing.

Status: Reviewed & tested by the community » Needs work
Issue tags: +PHPUnit, +MenuSystemRevamp, +WSCCI, +RTBC July 1

The last submitted patch, local-task-2004334-48.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
41.37 KB

rerolled for the expected services.yaml conflict.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

Manually tested, and the patch is identical.

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to 8.x. Thanks!

cweagans’s picture

Priority: Normal » Critical
Status: Fixed » Active
Issue tags: +Needs change record

This needs a change notice.

jibran’s picture

Title: Separate Tabs (MENU_LOCAL_TASK) from hook_menu() » Change notice: Separate Tabs (MENU_LOCAL_TASK) from hook_menu()

Updating title.

catch’s picture

+++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.phpundefined
@@ -0,0 +1,227 @@
+      // @todo - optimize this lookup by compiling or caching.

Was this profiled?

Is there a follow-up for the @todo?

pwolanin’s picture

@catch - I'm going to try to make sure all the change notices and follow-ups are in place tonight or tomorrow- have been busy/traveling since this was committed.

pwolanin’s picture

In fact since we are now extending DefaultPluginManager, getDefinitions() is already cached - I think that changed in #1903346: Establish a new DefaultPluginManager to encapsulate best practices

We should just remove the @todo I think.

tim.plunkett’s picture

DefaultPluginManager gives you static caching for free, you have to do some extra work in the constructor for persistent caching (which I don't know that we want anyway).

catch’s picture

(which I don't know that we want anyway).

That's why we should profile it ;)

pwolanin’s picture

We should either do persistent caching of the definitions, or for the set of local tasks found for a given route.

As the number of these plugins grows, I'd guess we'd want both? However, the trick will be to effectively clear this cache if e.g. there is a new plugin available (e.g. if Views adds a new derivative Plugin dynamically for a new tab).

tim.plunkett’s picture

$manager->clearCachedDefinitions()

Right now that clears the static cache, if we add persistent caching it will handle that as well.

pwolanin’s picture

Title: Change notice: Separate Tabs (MENU_LOCAL_TASK) from hook_menu() » Separate Tabs (MENU_LOCAL_TASK) from hook_menu()
Priority: Critical » Normal
Status: Active » Fixed
Issue tags: -Needs change record
jibran’s picture

Note that the MENU_LOCAL_TASK type did more than just present it as a local task (tab), it also manipulated the breadcrumb trail. In order to preserve that behavior each tab should have an entry of type MENU_VISIBLE_IN_BREADCRUMB left in hook_menu, at least until the breadcrumb code is made aware of the local task plugins.

Do we have an issue for this?

pwolanin’s picture

Should probably at least add this to #2026075: [meta] Remove drupal_set_breadcrumb and LegacyBreadcrumbBuilder

And/or we should simple rip all breadcrumbs out of core and have a null service by default.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

joachim’s picture

Title: Separate Tabs (MENU_LOCAL_TASK) from hook_menu() » Change notice: Separate Tabs (MENU_LOCAL_TASK) from hook_menu()
Status: Closed (fixed) » Active

Reopening.

The change notice at https://drupal.org/node/2044515 is not clear at all.

Round about this point:

"These example tabs are simply using the base class - and all the needed definition information is encapsulated in the YAML:"

Each local task is represented by a plugin [how?]. Each plugin definition (found in the yml file [which yml file? routing.yml, or another one?]) contains three to five loal-task specific keys underneath the top-level key which is the unique plugin ID: [how?]

dawehner’s picture

dawehner’s picture

Issue tags: +WSCCI

oh tags ...

pwolanin’s picture

Title: Change notice: Separate Tabs (MENU_LOCAL_TASK) from hook_menu() » Separate Tabs (MENU_LOCAL_TASK) from hook_menu()
Status: Active » Fixed

I think the change notice is ok: https://drupal.org/node/2044515

YesCT’s picture

I think we might use this, or make a meta to go through each module and convert their use of the hook to plugins.

there could be a problem though, See config_translation issue #2044737: Provide local tasks as plugins and core issue #2095325: using plugins to add tabs is not as capable as hook_menu()

joachim’s picture

The change record doesn't explain what the plugins are like -- what class are they? What plugin type?

> Note that you have full control over this behavior by implementing your own plugin class for a specific tab.

How do you do that?

pwolanin’s picture

These plugins can also have a different class or have derivatives. To implement those features, specify the class or derivatives key with the name of an appropriate class.

Just added a line indicating the default class.

Automatically closed -- issue fixed for 2 weeks with no activity.

Anonymous’s picture

Issue summary: View changes

add #2032587 to summary