Follow up to #2047633: Move definition of menu links to hook_menu_link_defaults(), decouple key name from path, and make 'parent' explicit. That issue introduced a hook_menu_link_defaults() as the D8 replacement for the menu link portion of D7's hook_menu(). However, several alternate ideas were presented on that issue and punted to here:

  1. Use YAML + optional YAML-specified callable + ALTER event, to match how routes are declared/altered.
  2. Leave the array based hook, but rename it to hook_menu_link_info().
  3. Use the Entity API exclusively: i.e., create the menu link entities directly in hook_install(), and update them in hook_update_N().
  4. Since the vast majority of the time, there's only 0 or 1 menu links per route, add menu link definitions to routing.yml, and then introduce some dedicated hook or event to handle the extreme edge cases.
  5. See #47 redefine menu links as plugins with YAML discover plus custom menu links with associated content entity in analogy to block + custom_block

Remaining tasks: decide on one of the above and implement it, or decide to leave hook_menu_link_defaults() as-is.

CommentFileSizeAuthor
#14 menu_link-direct-2178723-14.patch3.37 KBeffulgentsia
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

catch’s picture

Thoughts on all these;

#1. While it 'matches' routes, routes/local tasks etc. aren't entities, and we don't have a UI for creating/updating them. Sometimes it's better to be inconsistent, than misleadingly consistent.

#2 would be leaving the status quo of the current patch that's RTBC. I don't think anyone thinks that's great, but it's a relatively simple port from 7.x if we decide none of the other options are better.

#3 I really like. chx suggested that in 2010 and it seemed a good idea then too "Menu links should be simply saved from install files and not generated from router": #653784: Separate out menu links from hook_menu. We'll lose some features (like magic updating of menu items, this is only really useful for the admin menu structure, don't think I'd miss it for anything else), but those are features that are quite minor compared to the effort maintaining them.

#4 please no.

dawehner’s picture

Use the Entity API exclusively: i.e., create the menu link entities directly in hook_install(), and update them in hook_update_N().

If we do this, we would have to figure out how to properly "update" menu links when a view config is synched. Something like storing the used UUID somewhere could be one approach.

pwolanin’s picture

So one of the questions is if you update do you have to check if the link is customized? How would all this work with translations?

Also, as much as I think the port in #2047633 is the correct next step and needs to go in ASAP, It would be nice to find a way to improve the DX for the very common use case of creating a module admin page that should show up in the various admin listings.

catch’s picture

So one of the questions is if you update do you have to check if the link is customized?

Seems like that'd be optional, and depend on what the change is.

With translations, seems like if fields aren't translatable there's no problem. If they are it'd need to update the English version, then the others would be marked as outdated?

sun’s picture

Please note that 1-3 are attacking technical issues only, and out of those, (3) would certainly be most logical (although the still required auto-re-parenting logic might block it).

(4), however, addresses the DX/UX of defining menu links in code for the primary use-case of associating exactly 1 link with a route, which requires you to figure out 3 independent IDs:

Architectural internals aside, I think we should primarily focus on core product experience. → How hard can it be to place my module's menu link next to or below /admin/structure/types? — Are you able to give advice to a newcomer without looking at the code/definitions?


system.admin.config.system
∟ action.admin.actions
  ∟ mymodule.settings

→ Menu link machine names will be completely arbitrary and will make absolutely no sense.

Care to remind me, why can't we use the route name as key + parent again?

Aside from perhaps a few edge-case modules, I've the impression that diverging from our existing 1:1 route to link mapping only causes problems with no gain for the >95% of API consumers?

Perhaps the use-cases in my entire Drupal history were not advanced enough, but I never actually felt limited by that 1:1 mapping. I think we should explore whether we cannot move the "more than one link for a single route" edge-case/use-case to a different construct; e.g., a separate hook or routing event subscriber.


Perhaps — and this is borderline silly, but then again possibly not — we should go back to mixing "default menu link" information into the routing definitions?

(→ so as to remove two layers of indirection and go with direct route name references for the >98% use-case instead; the remaining poor 2% can happily use a bad-ass complex facility in order to be able to register multiple links for a single route, unless their use-case isn't to be solved via routes in the first place.)

Because I believe it would be a big win for DX, I'd like to better understand the reservations of others against this.

From my perspective, I'd understand the concerns, if we hadn't cleanly separated the internal subsystems yet. But given that this is the case already, it all boils down to finding the best DX for developers only.

Perhaps as a concrete example:

 node.overview_types:
   path: '/admin/structure/types'
   defaults:
     _content: '\Drupal\Core\Entity\Controller\EntityListController::listing'
     entity_type: 'node_type'
     _title: 'Content types'
   requirements:
     _permission: 'administer content types'
+  link:
+    parent: system.admin_structure
+    title: 'Content types'
+    description: 'Manage content types, including default status, front page promotion, comment settings, etc.'

In words:

  1. No more need to manually specify a link machine name.
  2. No more need to separately locate the route name to associate the link to.
  3. Instead of having dig out the parent link machine name (and very possibly also its associated route in order to validate that it is actually the link for the route that you intended), you simply specify the parent route name.
  4. If any of the related definitions is changed, there is only one place that needs to be updated.

The data is still cleanly separated. Their definition is merely moved to the most sensible place to achieve the best DX for module authors.

To produce the above example, I had to open exactly two files: node.routing.yml and system.routing.yml. In HEAD, that's at least four files (+ node.module + system.module).

Damien Tournoud’s picture

I agree with @sun here. Automatic definition of menu links from the router just makes sense. That's precisely why we have had that all those years.

(But of course, it's way better to use schema-less YAML files to define them then to use an anonymous arrays.)

The DX of #3 would be horrible for simple tasks, but another problem with it is that it solidly anchors menu links in the realm of content, instead of the one of configuration. In my experience, menu links tend to fall more frequently in "configuration" rather then "data", so this feels very backward.

Gábor Hojtsy’s picture

So #2047633: Move definition of menu links to hook_menu_link_defaults(), decouple key name from path, and make 'parent' explicit wanted to remove the runtime t()-ing of strings for items provided by modules, but it does not do that in its current (still uncommitted) form. If/once that is removed, the translations open up to be solved for the built-in items as well. That requires having menu titles translatable (or incurring *serious* regressions). I'm not fully up to date on the effects of #2047633: Move definition of menu links to hook_menu_link_defaults(), decouple key name from path, and make 'parent' explicit for translatability. The format to be used to set default menu items needs to be defined in a way to support translatability-integration as well especially when item updates are concerned.

effulgentsia’s picture

Because I believe it would be a big win for DX, I'd like to better understand the reservations of others against this.

I have mixed feelings on this option (#4 from the issue summary). I like the DX of it with respect to developers who only develop for Drupal, and not also for other Symfony or MVC systems. However, for people who cross operate with those systems, my reservation is that it's Drupal hijacking a file type that isn't ours. Symfony defines the concept of a *.routing.yml file and the top-level properties of a route (path, defaults, requirements). Us bolting on a "link" property (in the YAML) in order to add information that has absolutely nothing to do with routing (routing is what you do when a request to a URL hits the server, not how you display links to that URL from other pages) just feels a bit wrong.

The DX of #3 would be horrible for simple tasks, but another problem with it is that it solidly anchors menu links in the realm of content, instead of the one of configuration. In my experience, menu links tend to fall more frequently in "configuration" rather then "data", so this feels very backward.

With or without #3, I think for better or worse, menu links are already solidly anchored to content rather than config. That will especially become true once #1842858: [PP-1] Convert menu links to the new Entity Field API is done, and I don't see a viable suggestion to not do that issue, or to change course and convert menu links to config entities.

As to the DX of #3, are we sure it'll be horrible for simple tasks? Would it make sense to prototype / mock up some examples to evaluate?

catch’s picture

For me the 'simple tasks' is configuration pages under /admin, because we still have this hard-coded administration interface in system module, which in turn is based entirely on menu links.

ConfigFormBase and entity administration links gets us closer to a situation where administrative pages could be derived from information other than which routes/links happen to live under admin/* and its top-level directories, but we're obviously not there yet.

pwolanin’s picture

Well, if we are committed to using routes in Drupal 8, the PATH pattern admin/* shouldn't be used to determine anything, and in general paths are not canonical. So you always need both the route for the link and the machine name for the parent link.

re: #8 we do also need to start thinking of menu links as content - maybe more like book module than whatever we thought of them before.

So, possibly we should not be using this system to define the administrative interface at all?

If we want to "automate" it for the simplest cases, maybe the .info.yml file? Modules are already putting some links there using configure, e.g.:

configure: contact.category_list
moshe weitzman’s picture

I too am in favor of #3, for the reasons that catch alluded to in #1 - "We'll lose some features (like magic updating of menu items, this is only really useful for the admin menu structure, don't think I'd miss it for anything else), but those are features that are quite minor compared to the effort maintaining them.".

Menu link rebuilding is so painful, we had to invent a locking system to pause all requests until rebuild is done (more or less). I love #3 because it bypasses all that complexity and poor performance.

jhodgdon’s picture

Can we see an example of what proposal #3 would actually look like? How much trouble is this going to be for developers of contrib modules that want to have something listed on admin/config or admin/structure?

xjm’s picture

Yep, @effulgentsia said today that he will post a mockup of what proposal #3 would look like for a module implementing it.

The issue @DamZ raises with menu links being content (versus configuration) is a relevant concern, but I think it's out of scope for this issue. It also affects other content entities that might also be more like configuration in some circumstances; for example, taxonomy terms. So far we've chosen to make both entity types content, and the scope of CMI has been that we will leave it to contrib to solve the issue of content deployment or managing situations where something we have classified as content should be treated like configuration.

Having default menu items as configuration entities instead of content would still require something like #3 anyway. The difference would be that one could also provide the menu entities in the default configuration folder to be installed automatically, rather than a hook_install(). The problem of updating from modified default configuration on a module update is still unsolved for configuration as well. Conceptually, we would make a lot of the same changes as for #3 if they were configuration entities instead.

So, I'm also in favor of the proposal in #3.

effulgentsia’s picture

Status: Active » Needs review
FileSize
3.37 KB

How's this?

sun’s picture

This discussion still focuses exclusively on (1) API usage and (2) maintenance, but (3) ignores DX entirely.

Perhaps my brain is simply not able to make the logical connection, but:

How can there be an agreement on #2178725: Make all core menu link machine names and the corresponding route names match to synchronize route names and menu link machine names, but at the same time, the exact proposal that would truly ensure synchronization without human interaction in #5 is dismissed?

That proposal exclusively talks about (3) DX. Because (2) the topic of whether to maintain that info is a decision internal to the system, and (1) API usage is [mostly] abstracted away.

Can you enlighten me, please?

My fundamental stance is still the same: Humans are not able to reliably synchronize any data set that is larger than 3 items. — Do NOT rely on humans for any task like that.


PS: It also helps to clarify that people are in support of option 3) in the issue summary (as opposed to comment #3) when referring to #3...

jhodgdon’s picture

Issue tags: +API change

RE comment #14, the proposal for how modules would create menu links seems OK to me. As compared to hook_menu_link_defaults(), it is pretty similar and only a line or two more of code (because you have to put in the module and machine name into the function call, and call save() afterwards).

Some improvements:
- It would be really helpful if this function menu_link_create() -- which many many module developers are going to need to use, and they probably have zero use for really digging into the menu link entity otherwise -- had some documentation on which array elements in $values are actually required to create a menu link. And as you can see from the code, there are some special things like $values['description'] and $values['parent'], which are not part of the menu link entity at all. So, docs are necessary.
- If this menu_link_create() function is primarily for use in the hook_install() for modules to create their menu links, why not let it go ahead and call ->save() inside, rather than forcing developers to do that everywhere? Are there really use cases where someone would call this function and not immediately call ->save()?

RE comment #15... It seems like the main concern you are raising (if I am understanding correctly?) is that this proposal does not encourage/force people to use the same machine name for the link as the route, although we would prefer that people do that. So... Would it be possible for the new menu_link_create() function to do something like this:

if (!isset($values['machine_name'])) {
   $values['machine_name'] = $values['route'];
}

It really seems like most modules that create menu links are just trying to get something displayed on the Configuration or Structure page, and they don't care about what the machine name is for the link. At all. Why not make it easy on them and assign it automatically? There are already helpers in there for description and parent... why not one more?

Also, regarding the patch in #14 and concerns in #15, it looks like this proposed specific example of a menu link for Actions does not follow the convention of "route machine name and link machine name match"... ???

pwolanin’s picture

Since multiple links (in the same or different menus) can point to the same route (with the same or different route parameters) forcing the machine name to be the same as the route name is fundamentally a flawed proposal.

I also fear that doing this at install time open a real can or worms around correctly building the correct hierarchy. We depend on finding all links as part of the current process, while install-time means (I think?) that you only get the data for the one module being installed at a time?

In terms of performance, I think we should figure out a way to run this hook only when a module is enabled or uninstalled? Modules that want to programmatically create/remove links (e.g. Views) should be using the entity API.

jhodgdon’s picture

That is a really good point about the route parameters, and I don't think anyone is advocating forcing all menu entries' machine names to match their route machine names. But I think most modules that would want to declare menu entries would be so they can get listed in the whatever-it-is-now, Tools menu? The one that generates the Structure and Configuration admin landing pages. And in most cases there, they probably should use the same machine name for their route and menu entry. So why not have the API function they have to use have this default?

effulgentsia’s picture

it looks like this proposed specific example of a menu link for Actions does not follow the convention of "route machine name and link machine name match"... ???

Yes, see #2178725: Make all core menu link machine names and the corresponding route names match. There's not consensus on that issue yet, so more opinions could be helpful.

effulgentsia’s picture

How can there be an agreement on #2178725: Make all core menu link machine names and the corresponding route names match to synchronize route names and menu link machine names, but at the same time, the exact proposal that would truly ensure synchronization without human interaction in #5 is dismissed?

I tried to express my reservations about comment #5 in comment #8; here's a summary/update:

  1. The *.routing.yml file is defined and documented by Symfony. If we start adding arbitrary things in there, then it's a WTF for developers who read the Symfony docs and then wonder why we're being different.
  2. The above is especially bad if what we add in there has nothing to do with routing.
  3. Per comments #1 and #11, catch's and Moshe's support for issue summary option #3 is that it removes (most of) a menu link rebuilding step on every cache clear. But, if we do comment #5, then removing the menu link rebuild step would be a WTF (hey, why does some of what I change in this YAML take effect after cache clear, and other stuff doesn't?).

Meanwhile, we don't need true, 100% guaranteed, synchronization. Across the entirety of link generation systems, we can't have that anyway: for example, our *.local_tasks.yml are both human fallible and require default subtabs to have a different ID than their parent tab even though both link to the same route. Even if we only consider menu link entities, if due to human fallibility, D8 core or a contrib module is released with one or two mismatches, it's not the end of the world. And once released, neither route IDs nor menu link IDs should be changed, so maintaining synchronization over time (within a major version) is a non-issue.

Per #8, I'm not completely against issue summary option #4 / comment #5. If we can't do issue summary option #3, then that might be the next best thing despite my concerns about hijacking the routing file. But if we can do #3, then I prefer we just directly do it, and not add a potentially confusing layer over it.

effulgentsia’s picture

I also fear that doing this at install time open a real can or worms around correctly building the correct hierarchy. We depend on finding all links as part of the current process, while install-time means (I think?) that you only get the data for the one module being installed at a time?

If a module's hook_install() calls menu_link_create() with a particular 'parent', that usually means the module has a dependency on the module that provides that parent. Which means its install hook ran first and menu_link_create() has the data it needs in the {menu_link} table (or alternate storage backend). If it's an optional dependency, then the hook_install() needs to do a module_exists() call and conditionally set 'parent' to something that exists, same as comment_menu_link_defaults() does currently in HEAD. So, where is the potential for there being a problem with #14?

jhodgdon’s picture

So... Thinking about the proposal (3) from the issue summary, as in the patch in comment #14, am once again confused about the DX of how this would work during the module development process.

So if I am developing a module, let's say I put some stuff in my routing.yml file to define where my settings page is, and I implement hook_install() to define my admin menu link to that page to be on Configure.

Then I (oops!) realize that I had a typo in the route machine name. So I edit my routing.yml file and the hook_install() and clear the cache. Would I need to uninstall/reinstall the module in order for the menu link to be recreated correctly? It seems like I would... but actually, I guess my bad link would still be there after uninstalling -- do modules also need to implement hook_uninstall() and remove the link, or would links to non-existent routes automatically go away? Or would I need to do a hook_update_N() during my development process (ugh)?

None of this seems too great for the DX of a module developer who's early in the development process and editing things right and left, if I'm understanding correctly. Is there one of the proposals that would work better for this? And does the current system of hook_menu_link_defaults() work better for DX?

effulgentsia’s picture

do modules also need to implement hook_uninstall() and remove the link, or would links to non-existent routes automatically go away?

#14 contains an action_uninstall(). We could potentially implement this in a single place on behalf of all modules as a hook_modules_uninstalled() implementation (e.g., menu_link_modules_uninstalled()). However, I don't know yet if we want to standardize/force a particular content removal decision on all modules like that. We do that for config, but we haven't really discussed yet whether to do that for content.

Is there one of the proposals that would work better for this?

Both HEAD and all of the other proposals retain the concept of menu link rebuilding after cache clear, which makes things easier during early module development for cases where you're making frequent changes to your module-defined menu links. However, with all module-defined config, as well as with all other module-defined content, you need to uninstall/reinstall or implement hook_update_N(). So why should we continue to treat menu links as different than that? Perhaps it's because routes and local tasks *are* rebuilt every cache clear, but routes and local tasks are neither config nor content -- they're completely unchangeable via (core) UI.

pwolanin’s picture

Right, so here's my proposal:

  • Move the hook into the module .install file
  • Restrict menu link rebuilds to module install and uninstall
  • Have a button some place (e.g. menu ui module?) that does a manual rebuild
  • Ask drush to add a menu links rebuild option.
jhodgdon’s picture

Wow, that proposal in comment #24 sounds almost sane! :)

catch’s picture

@effulgentsia

However, I don't know yet if we want to standardize/force a particular content removal decision on all modules like that. We do that for config, but we haven't really discussed yet whether to do that for content.

It'd be straightforward enough to remove links for which there's no corresponding route, that'd take care of admin routes easily enough.

Where it gets tricky is if a module adds a link for the route of another module. In that case I don't see any particular reason to remove (nor for config entities where there's no actual dependency). For those you can always just go in and delete. Annoying if you're trying out a module, less so if your site is actually using stuff it provides and you didn't know where it came from.

@pwolanin
Moving the hook to .install is interesting - worth adding that as a new option to the issue summary?

sun’s picture

How does the explicit install/update/uninstall approach work for menu links that depend on another module, which wasn't installed yet when my module was installed, but gets installed later?

Will I have to implement hook_modules_installed() to check every module installation for whether that module has been installed, in order to create my menu links...?

jhodgdon’s picture

RE comment #27 - how can you install a module before one that it depends on? That isn't supposed to happen?

effulgentsia’s picture

how can you install a module before one that it depends on?

You can't, but #27 is referring to more of a soft dependency. For example, module A does not strictly depend on module B, but if module B exists, then module A adds some additional menu links. With issue summary option #3, yes, that would require a hook_modules_installed(), which I don't think is so bad, as the case isn't that common. With comment #24, I guess that would be handled automatically if the installation/uninstallation of any module triggers a full link rebuild.

If we go with #24, then why keep it a hook? Seems like then we're back to also needing to consider issue summary options #1 and #4. From comment #1:

[issue summary option] #1. While it 'matches' routes, routes/local tasks etc. aren't entities, and we don't have a UI for creating/updating them. Sometimes it's better to be inconsistent, than misleadingly consistent.

I don't see how the presence or lack of UI is all that relevant. For example, contrib can create a UI for editing route information, but that wouldn't invalidate the concept of a routing.yml file. What matters more is architecture, and so long as we have a rebuild step on cache clear, manual triggered, or whenever, then what a *.menu_link.yml (or other name TBD) would define isn't menu link entities themselves, but module-defined declarative information that the menu system then uses to create entities based on its logic.

[issue summary option] #4. please no.

Care to elaborate? I've shared my concerns in #20, but as I mentioned there, those concerns aren't necessarily hard blockers for me.

catch’s picture

This is a problem for soft dependencies in config entities as well:

#2082117: Install default config only when the owner and provider modules are both enabled
#1776830: [META-1] Installation and uninstallation of configuration provided by a module that belongs to another module's API
and etc.

On option #4 it's the mixing of information in the routing file, especially after so much effort has been expended to clearly separate all of this out.

catch’s picture

pwolanin’s picture

We left this as a hook because we didn't have a better idea for default content.

I feel like it's fine to ship as-is and we are wasting effort on a non-problem, versus actually dealing with all the entity API conversions and other things that need to happen.

If it's contributing to excessively slow rebuilds, then my suggestion in #24 would help alleviate that.

jhodgdon’s picture

OK, can we add this as proposal #5 in the issue summary, to take into account that Module A might want to define different menu links based on whether module B is enabled or not?

5. Leave it as a hook, but put it into the .install file. Call the hook on all newly and previously enabled modules when any module is either installed or uninstalled, to rebuild the menu links. Have a button someplace (e.g., menu ui module?) that does a manual rebuild, and ask drush to add a menu links rebuild command.

jhodgdon’s picture

Actually, maybe the best spot for the button would be next to the "clear cache" button on Performance, if "clear cache" would not also rebuild the menus?

pwolanin’s picture

I would see this new button as 95%+ a developer tool - it's not really performance related.

jhodgdon’s picture

Clearing the cache is also 95% a developer tool. :) Agreed that rebuilding the menu is not performance-related at all; however, if you need to rebuild the menu, it's probably also true 95% of the time that you need to clear the cache (and at least sometimes, vice versa). For DX, I think it would be nice if they were available on the same page, for situations where you happen to be working in the UI (testing your new site feature etc.) rather than using Drush.

Or maybe the "rebuild the menu" button could just have another button next to it that is "rebuild the menu and clear the cache"?

tim.plunkett’s picture

That's why it's under admin/config/development...

effulgentsia’s picture

We left this as a hook because we didn't have a better idea for default content.

This is the premise that I object to. Here's why. D8 core has no existing API or pattern for declarative module-provided content entities. For config, we have YAML files in a config directory, but for content, if your module wants to programmatically add nodes, taxonomy terms, etc., it needs to do it imperatively (in hook_install() or elsewhere) rather than declaratively. However, if we were to create a generalized declarative API for content entities, I do not think we would do it as a hook; I would imagine we would do it as YAML files in a "content" directory to mirror config. I'm not suggesting we add such an API to D8 core (it's probably too late for that), but if a contrib project pops up to do so (and I hope it does, cause that would be awesome for distribution builders), I would hope it would do it that way, and not as a hook. And, I think that having an example of a one-off hook_ENTITY_TYPE_defaults() in core sends the wrong message for how contrib should tackle that problem space.

I feel like it's fine to ship as-is

It would be fine in the sense of working and people could get used to it without too much trouble, but I think it's indefensible as a correct API. In terms of applying D8 patterns consistently, I see two sensible approaches:

  1. Issue summary option #3: Treat menu links as content entities that don't deserve/require any special treatment. Create them in hook_install() exactly the same way that you would create nodes, terms, or any other content. Rip out all the code that deals with menu link rebuilding.
  2. Issue summary option #1: Due to it being so common for modules to provide menu links, treat them as special by providing a declarative API and rebuilding facility. In which case, make that declarative API consistent with the rest of the menu system: YAML files. Catch objects to that in #1.1, because that risks misleading people into not recognizing the architectural difference between local tasks (non-UI-editable, non-entities) and menu links (UI-editable entities), but per #29, if our intention is to allow module developers to think of menu links as things to declare, then I don't see why, for the purposes of declaring them, they need to care about those under-the-hood differences.

I can see the difficulty in us reaching consensus on one of the above two, but I think it would be a shame if our failure to do that results in us sticking with the hook, which, IMO, is the worst of all options.

and we are wasting effort

That's a valid concern to raise. Certainly there are plenty of functional bugs and other DX issues to spend time on. @catch, @webchick, others: what do you think?

jhodgdon’s picture

RE #38 - well there's the root of the problem: Someone apparently made a decision at some point that menu items are Content.

It seems to me that providing a link to a module's main config page, from either Structure or Config or whatever, is really a very basic thing that pretty much every module needs to do -- and "The location of the main config page" and "whether this page logically belongs in Structure or Config and in what category" also seem like very basic module information. So telling Drupal this information should be straightforward, and it isn't really very logically like "I want my module to create a content item and then make sure it goes away when my module is uninstalled". In other words, I don't think it's "Content". I'm not even sure it's "Configuration" really... is that really something you want people to change?

So... I realize it is most likely way too late to change the fact that (apparently?) menu items are Content and not Config, and it is also probably too late to change the fact that (apparently?) the Config and Structure pages are generated from Menu content items... but these decisions seem to be problematic for the very basic need of nearly all modules to have a straightforward way to tell Drupal where their config page belongs.

effulgentsia’s picture

RE #38 - well there's the root of the problem: Someone apparently made a decision at some point that menu items are Content.

Yep. Damien pointed out the same in #6. Where I think that was decided was in #916388-48: Convert menu links into entities and the patch that followed. Though I didn't read all the earlier comments to see what factors led to that decision, my incomplete understanding is something like this:

  • Menu links are editable in the UI. The admin can change their title, parent, and other info. That's how it's been in all prior versions of Drupal, and other than #39, I'm not aware of anyone having suggested changing that.
  • To reuse access control APIs, translation APIs, CRUD APIs, etc., it therefore makes sense for them to be entities.
  • To be translatable, they need to be either config entities or content entities. There's no 3rd kind of entity in Drupal.
  • For some combination of reasons (to make them fieldable? because config entities are 1 per YAML file and that would be annoying for menu links? because there can be a lot of them and we have no efficient querying implementation for config entities? to match the split we have for vocabularies/terms?), content seemed to offer more benefits than config.

If we really believe that menu links being content entities is an unfortunate WTF that we have to live with, but would prefer to not have to force the consequences of that onto every module developer, then I think we're left with either issue summary option #1, or keeping the hook_menu_link_defaults() hook but just moving it to the .install file. I've made my preference on that clear in #38, but we're obviously far from consensus on that.

catch’s picture

effulgentsia's summary of the reasons is pretty good. I'd add that they are often programmatically created along with content entities (i.e. core did this until the book patch landed a couple of days ago), and that would get very messy with syncing configuration. Similarly per-user shortcuts were using menu links until fairly recently too - don't want those staged either.

We have a similar conceptual/implementation problem with path aliases, except we didn't even try to resolve that one at all yet and left them as a 100% special case.

Catch objects to that in #1.1, because that risks misleading people into not recognizing the architectural difference between local tasks (non-UI-editable, non-entities) and menu links (UI-editable entities)

I also think a bad implementation of content entity defaults in YAML would be worse than the current implementation in a hook. With the hook we can hold our hands up and say sorry we didn't have any better ideas. With YAML it's not going to be a proper default content API at all, but it might superficially look like an attempt to create one. And if we bake it into the router files then come up with something better for 9.x, we'll need to go into all the routing YAML and remove it again, whereas the hook is self contained.

and it is also probably too late to change the fact that (apparently?) the Config and Structure pages are generated from Menu content items...

If we had declarative admin navigation not using menu links, then just let people create menu links in hook_install() for other stuff, that would solve some of the issues here. While no-one is working on this at all, I do not think it's necessarily too late to change that - after all we're discussing changing this API and 99% of the difficulties are due to administrative link handling.

#1316692: Convert hook_admin_paths() into declarative properties on routes is relevant here, but we'd need to add descriptions and top level categories /somewhere/ to re-implement what we currently have at a minimum.

Gábor Hojtsy’s picture

Yeah adding to what catch said, menu items are often tied to nodes too like related taxonomy terms and uploaded files, and therefore make more sense to be considered content attached to other content (like terms and files). Admin menu links are different but I've only seen proposed recently that they should use their own system instead of "the menu system". That would be interesting but way too late now?! I think.

Dries’s picture

Given that the menu system is something that almost all module developers are being exposed to, I would really like to see us move to YML files for this, for consistency with the rest of the system. Whether that means changing admin menu links from content entities to something else or not, I leave to you all to hash out. I think fixing this API is more important than fixing other API that are more 'under the hood' and only used by a select few.

jhodgdon’s picture

RE #42, catch said in #41 that it is "not necessarily too late" to change admin navigation to not use menu links.

So, if I can summarize recent discussions:

a) There are good reasons for having menu links be Content Entities, in general (see comment #40 for a nice summary).

b) There aren't really compelling reasons for having admin menu navigation be Menu Link entities, because they're just created by modules, and it's not really necessary to have the ability to edit in the UI and add fields to etc. The reasons that they are menu item entities are: (1) they always have been menu links in the past and (2) they do have a hierarchical structure, so they share some structure with menu links.

c) Nearly every module has at least one admin navigation item to declare, so it would be good DX if declaring admin navigation items is easy to do (and a major DX regression if it is difficult to do). Because they are closely tied to routes, YML is probably preferable to a hook.

d) Creating Menu Item content entities and making sure they are updated/deleted appropriately would be a pain for a module developer (bad DX).

e) catch has stated that it may not be too late to change how the admin navigation works.

====> Could we (in a reasonable amount of time):

Devise a YML-file-based system for modules to declare their admin navigation items (including a way to declare the categories, such as Structure, Configuration, Configuration/Development, etc.), and caching/storing the admin navigation efficiently, with a "Rebuild" button somewhere (and otherwise assuming that the only time we need to rebuild the admin nav is when a module is installed/uninstalled).

dawehner’s picture

Devise a YML-file-based system for modules to declare their admin navigation items (including a way to declare the categories, such as Structure, Configuration, Configuration/Development, etc.), and caching/storing the admin navigation efficiently, with a "Rebuild" button somewhere (and otherwise assuming that the only time we need to rebuild the admin nav is when a module is installed/uninstalled).

If we do make the admin navigation a 100% different subsystem, we will see problems when people use tools like panels/views
to build their own custom admin pages. Admin navigation is certainly not that static as you think.
The problem comes down that config entities "dynamically" create content entities. The current hook_menu_link_defaults rebuilding
handles that pretty nice automatically, so I would like the first alternative.

Gábor Hojtsy’s picture

@dawehner: well, then if views would need to define an admin menu item, it would need to create a config entity then I guess? The problem is Views, etc. would need to have duplicate solutions, if the person wants to place it in /admin or /somethingelse, different code paths would run and different setup would be needed for items.

BTW a static YAML approach would work really well for translation, since we don't need to be bothered with updating the items. Especially if using config entities for that, then translation is already solved :)

pwolanin’s picture

There is really a deep split in the desired behavior of menu links. Some of them are very close to configuration or plugin definitions, and are rarely modified by site admins, while others are clearly content, and we want them to behave like translatable entities.

Discussing in person with xjm, dawehner, and Gabor, here's a slightly radical proposal, but which might actually satisfy the competing concerns here:

Taking a lead from the block system (and local tasks and local actions) let's make the base implementation of menu links be a plugin using YAML discovery. The custom menu links would be plugin derivates each of which has an associated content entity (like custom_block).

  • The most common DX would be also defining a link in a YAML file that's similar to the admin route they just defined.
  • As with local actions/tasks if you really need to do something special, you can provide a non-default class.
  • The localization team has already been working out how to localize the titles from local task and action YAML files, so this could be easily extended to these module-provided menu links.
  • The content entities corresponding to each custom link could use our existing machinery for content entity translation.

Some further thought/details:

  • The hierarchy can essentially be reconstructed by knowing the menu name and parent ID, so the content entity should not have any knowledge of the SQL optimized storage of the hierarch (i.e. p1-p9 columns, etc).
  • In place of using a simple cache, the menu link plugin manager can still use essentially the menu tree service proposed in #2207893: Convert menu tree building to a service..
  • The current enabled/disabled state of each link in tracked by the manager, even though a default value is in the YAML.
  • You would not be able to delete or edit in the menu UI the title of a module-provided link, but you could disable it and replace it with a custom link.
  • Views module would define plugin derivates for menu links it creates, as it does now for blocks
xjm’s picture

@dawehner also made the suggestion that we could break these changes into more manageable chunks by converting to the yaml definitions first, so that the public API is complete for contributed module developers, and then continue architecting it properly internally once that change is made.

dawehner’s picture

The localization team has already been working out how to localize the titles from local task and action YAML files, so this could be easily extended to these module-provided menu links.

To clarify this bit: For non default yml file ones we can extend the class and not call t(), for the ones defines in static yml files we will call t() and this is it.

+1 for the summary

Gábor Hojtsy’s picture

@dawehner: agree with that translation direction.

jhodgdon’s picture

+1 for the proposal in #47. Maybe it should be added to the issue summary?

pwolanin’s picture

Issue summary: View changes

added to the issue summary

jhodgdon’s picture

Um. I think it should have been added to the end of the list, because previous comments already were referring to "proposal 1 from the issue summary" etc.

jhodgdon’s picture

Issue summary: View changes

Moved it, it's now proposal #5.

effulgentsia’s picture

I like #47 a lot. Question about it:

You would not be able to delete or edit in the menu UI the title of a module-provided link, but you could disable it and replace it with a custom link.

Does this apply to parent and weight as well as to title? I would imagine so. Is that acceptable in terms of feature/UX? Also, if you then have a menu with a mix of custom and noncustom links, is it possible to have drag-and-drop reordering if custom ones can have editable weight, but noncustom can't?

pwolanin’s picture

Discussion last night seemed to have some reasonable comfort going forward with #47, though amateescu expressed some misgivings and desire to continue down the path purely of content entities.

An amendment to the plan concerns customizing the module-provided menu links. For those that are customized we could create a config entity per link only when it's customized OR (likely more performant) have a single config for all admin links. In either case this config would essentially record the different compared to the default for parent (maybe? using a uuid?), weight and enabled/disable. We would still block title editing so as to preserve localization. This could allow changes to the admin section to be deployable.

All the menu link plugins would have a way to invoke methods on save that allow custom data to be persisted.

In the case of module-provided links, this would involve updating the config. For custom menu links, updating the content entity. for e.g. views-provided links, updating the associated view config entity.

effulgentsia’s picture

For those that are customized we could create a config entity per link only when it's customized OR (likely more performant) have a single config for all admin links.

Single config seems like a great idea for that.

We would still block title editing so as to preserve localization.

I think it's okay to disallow title editing if we need to, but why is it necessary if we have a config file for recording customizations? Config files can be localized.

pwolanin’s picture

User entered strings in config should not be localized (passed though t()). However, we should get other people to weigh in on this part.

effulgentsia’s picture

User entered strings in config should not be localized (passed though t()).

Yes. Absolutely. I meant, config files can have translations. I.e., Views titles and Site name can be translated, so why can't customized menu link titles?

dawehner’s picture

Here is step one: use the yml files: #2226903: Step 1: Move static menu links to yml files

pwolanin’s picture

@effulgentsia - I think one of the most serious Drupal WTF moments (though one people rarely discover) is that admin links are localized normally, but if you edit them, might suddenly stop being localized. I think that violates the principle of least surprise :)

dawehner’s picture

pwolanin’s picture

Status: Needs review » Fixed

Can we call this fixed? Do we need a new Meta issue?

Andre-B’s picture

@pwolanin meta issue would be great, so once everything is done contrib maintainers can adjust the recent changes (again).

xjm’s picture

Title: Finalize module-facing API of declaring menu links » [meta] Finalize module-facing API of declaring menu links
Status: Fixed » Active

Let's use this as a meta. I nearly had a heart attack when I saw this marked fixed. ;)

xjm’s picture

Title: [meta] Finalize module-facing API of declaring menu links » [meta-2] Finalize module-facing API of declaring menu links
Crell’s picture

I'm just now catching up here. So that's what broke my demo module right before MidCamp... :-)

I am actually extremely wary here. We've already put a ton of work into making links entities, because entities are designed to scale. The old menu links system supported tens of thousands of links without falling over.

I am fairly confident that a content entity can scale to "tens of thousands". If it can't, then we're kinda hosed anyway because there are Drupal sites with millions of nodes.

I am not at all confident (due to lack of data) that config entities or plugins can scale to thousands. Blocks are scaling to dozens, not thousands. Are we talking about having *tens of thousands* of menu link derivative plugins? Can the system even handle that? I don't know, but we've never pushed it that far and I am very very worried that going down this route will come back to haunt us for years to come.

amateescu’s picture

That's exactly the concern I raised when this plan was proposed/discussed in a call during Dev Days.

effulgentsia’s picture

I am fairly confident that a content entity can scale to "tens of thousands"... I am not at all confident (due to lack of data) that config entities or plugins can scale to thousands....Are we talking about having *tens of thousands* of menu link derivative plugins?

The current proposal does not involve config entities, so we just need to address the plugin scaling question. First of all, even with content entities, there are ways to use the entity APIs that would lead to scaling problems. For example, entity_load_multiple_by_properties('node', array('type'=>'article')) would run out of memory if there are more article nodes than can fit into memory. Similarly, having a derivative plugin for every custom menu link would be a problem if there's calling code that invokes $menu_link_manager->getDefinitions(). So code that works with a menu link manager would need to not do that, just as application code needs to be careful when calling entity_load_multiple*(). Instead, we'll need to add methods to MenuLinkManager for getting the subset of definitions that are needed (e.g., only those that are in a certain menu, or have a certain parent, etc.). Also, we won't be able to use the default plugin manager caching strategy of caching all definitions in a single cache entry; instead, we'll probably only want to cache the base definitions, and then add in the derivatives at runtime (which should be fast as the underlying content entity will have its own cache once #597236: Add entity caching to core lands). Other than that, I don't see where the plugin approach will run into scaling issues. But I do appreciate the concern about this being new territory for us, possibly filled with problems we won't anticipate until we hit them.

catch’s picture

Agreed with #69, there's potentially new problems with this, but some of them are obvious and can be ameliorated.

Note I would be fine with forcing static/config entity links for admin menus, and content entity menu links anywhere else (i.e. split the system in two, avoiding the need for content menu link plugin derivatives), but I think I'm the only person that thought that was an entirely OK restriction when this came up.

amateescu’s picture

You weren't the only one ;)

catch’s picture

@amateescu, that's good to know. So I think it's OK to proceed with static + config entities as menu link plugins. Then when we get to fitting content entities to work with that, we should be prepared for the possibility that it just won't, in which case splitting it is still possible at that point. Or maybe we should re-open that discussion in a dedicated issue now?

webchick’s picture

For whatever it's worth, the distinction between "module-provided thingies" and "custom UI-created thingies" makes sense to me, too. We have already established this pattern with blocks, so it's something both developers and end-users will have some familiarity with.

The only thing I'd ask is that we take care to make the UI for "using" the thingies look and work the same regardless of what the underlying implementation looks like. In other words, we have one "place blocks" UI, and it lists both module-provided/custom-created blocks. The end user shouldn't need to know the origin of these menu links when building out their menus; the UI should work the same for both.

Gábor Hojtsy’s picture

I'd like to reiterate what @effulgentsia said in #69 above, "The current proposal does not involve config entities", the proposed static YAML solution for admin menu items would be similar to the current local tasks and local actions solution, not as a config entity.

catch’s picture

@Gabor, in Szeged we discussed that it would be necessary to use configuration entities (or a state() entry but that sounded bad to me) in the case that a module-provided link is overridden (title/parent/weight/disabled). Has that plan changed?

If we're not going to allow admin menu links to be overridden by the UI, then I don't see any reason for those to be treated as menu links at all - they could then use exactly the same approach to tasks/actions - just parse the YAML and create a UI from that. No editing at all.

effulgentsia’s picture

#56 says:

An amendment to the plan concerns customizing the module-provided menu links. For those that are customized we could create a config entity per link only when it's customized OR (likely more performant) have a single config for all admin links.

The second part of that OR statement sounds way more sane.

Gábor Hojtsy’s picture

Yeah it makes sense to somehow store the customisations in config yup.

pwolanin’s picture

I've posted a new meta issue for those who wish to discuss the plan we've developed to use plugins #2256497: [meta] Menu Links - New Plan for the Homestretch

I propose to mark this issue fixed if that plan is acceptable.

xjm’s picture

Issue tags: -beta blocker

Removing beta blocker tag in favor of #2256497: [meta] Menu Links - New Plan for the Homestretch since this is a meta.

effulgentsia’s picture

Status: Active » Closed (duplicate)

If #79 is ok to do, then I think closing this as a duplicate is even better. If that plan isn't accepted, then we can reopen this as a beta blocker.

In some ways, I actually see this issue as "fixed" in that "declaring menu links" has been finalized as of #2226903: Step 1: Move static menu links to yml files. However, #2256497: [meta] Menu Links - New Plan for the Homestretch or similar is needed to make the underlying system coherent with those declarations.

pwolanin’s picture

Status: Closed (duplicate) » Closed (fixed)