Closed (fixed)
Project:
Drupal core
Version:
11.x-dev
Component:
routing system
Priority:
Normal
Category:
Feature request
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
14 Apr 2026 at 21:50 UTC
Updated:
20 Jul 2026 at 05:20 UTC
Jump to comment: Most recent
Comments
Comment #3
godotislateGot a start on this that seems to work OK: MR 15476.
Introduced a new attribute FormRoute that just extends the Symfony Route attribute. This is done to separate form route discovery from regular controller discovery, and also FormRoute is limited to targeting only classes, not methods. I've allowed multiple FormRoute attribute to be applied on one class, to allow for multiple routes to use the same form, but I'm not sure whether that's necessary or should be supported. It's a straightforward change if not.
Also, this adds all the classes/files in the Form namespace/directory to discovery. I think it should be technically possible to use
Routeattributes on classes (and methods) in\Form, andFormRouteattributes on classes in\Controlleras well, though in the later case it won't work unless those classes implementFormInterface. That being said, I haven't tested this and I don't think this kind of usage should be promoted.I think I found a bug in AttributeRouteDiscovery;
hasMethod returns a boolean, which would make the identity comparison to 0 always FALSE, so I changed it to
I think this is the expected result, but not sure.
Lastly, ran into test failures because layout_builder does not have a dependency on field_ui, but
Drupal\layout_builder\Form\LayoutBuilderEntityViewDisplayFormextendsDrupal\field_ui\Form\EntityViewDisplayEditForm. So checking whether LayoutBuilderEntityViewDisplayForm exists with class_exists throws an exception when field_ui is not installed. In PHP 8.5, it's straightforward to handle this by catching the exception. For PHP < 8.5, we've had much sadness in plugin attribute discovery dealing with the fact that missing traits are fatal errors, not exceptions. I'm not sure if we want to bring that sadness over to route discovery, but it might be necessary.Comment #4
longwaveFor easier DX can we reuse the #[Route] attribute and detect internally whether we need to treat it as a form or a controller?
Comment #5
godotislateYes, we could base it off either namespace detection or whether an object implements FormInterface.
If the first thing, then no form routes would be allowed in \Controller, and no controller routes allowed in \Form, which should be fine I think.
And in either case, that means that Route attributes applied to methods on a form would be ignored, which I think would be fine too.
Edited to add: Actually, we should always do the FormInterface detection. We could add the namespace detection as well if we don't want any forms in \Controller.
Comment #6
godotislateRemoved FormRoute and made this change.
Comment #7
godotislateStarted a CR: https://www.drupal.org/node/3585342.
Since I removed the new attribute and a sample form route was converted, not sure what's left for tests, so I'll move to NR and add any tests from feedback.
Comment #8
godotislateAdded some test coverage.
Comment #9
longwaveLooking good, thanks for working on this. Added some questions/suggestions.
What do you think we should do about the traits issue in older PHP?
Comment #10
longwaveIf we land this in 11.4 we should perhaps consider combining the change record with https://www.drupal.org/node/3324758 instead of two separate ones that kinda say the same thing.
Comment #11
godotislateApplied the suggestion. Not sure either way whether it's worth detecting Route attributes on methods in the Form class to throw an error, but I can make that change if we think it's best.
We could use the MissingClassDetectionClassLoader the same as plugin discovery. I think it's worked pretty well, other than being a bit ugly to implement. There is one case it would not handle:
Drupal\Component\Discovery\StubTraitThis popped up in plugin discovery per #3255804-35: Hidden dependency on block_content in layout_builder, and we ended up having to move the trait. It seems less likely to me that a controller or form class would need to be instantiated somehow in the same request as module installation, so maybe it's more of an edge case here?
Yeah, is that something we can do right before it's committed? Change the URLs in the MR and edit the original CR?
Comment #12
godotislateActually, there aren't any CR URLs in the MR, so if this lands in time, we can just update the original CR instead.
Started to copy the trait handling stuff from plugin discovery over, but it's a lot. That'll wait for another day, or maybe a follow up.
Comment #13
godotislateI stubbed a follow up #3586328: [11.x] Handle attribute discovery fatal errors on missing traits for routing and other subsystems to handle missing traits, to keep scope here down. We can consider there whether just to copy out what we need from plugin discovery to route discovery, or whether it's possible to extract the missing trait handling to somewhere it can be used for attribute discovery across multiple subsystems, including hooks now or possibly services in the future.
I think feedback otherwise has been addressed, so back to NR.
Comment #14
longwaveThis is looking close, hopefully we can land this in 11.4.
Added a suggestion for assert() instead of logging, and also some more ways to clean up the Controller/Form split.
Comment #15
godotislateAddressed MR feedback. Back to NR.
Comment #16
godotislateResolved a merge conflict in system.routing.yml after #3584823: Convert system module routes to use attributes was merged to main.
Comment #17
godotislate@berdir brought up in Slack with me and @longwave that he hit an error with route discovery because of a controller class with an optional dependency. The exception thrown by a missing class/interface/trait wasn't caught in the origin attribute routing issue, but it is addressed in this issue's MR. Noting here that catching that exception should probably be a priority for 11.4 beta, so if this one doesn't make it, we should create a separate issue to handle that.
Comment #18
longwaveAdded a couple of questions, mostly just nitpicking over structure.
Comment #19
godotislateMade fixes for or otherwise addressed MR comments.
Comment #20
longwaveResolved all threads, no further comments, let's get this in!
Comment #21
godotislateIf this goes in 11.4, I think the CR here shouldn't be published. We'll just add to https://www.drupal.org/node/3324758.
Comment #22
godotislateRebased for merge conflict after #3592887: AttributeRouteDiscovery does not cleanly handle invalid classes
Comment #23
godotislateComment #24
catchThis looks really good, I don't think I have any actual comments on the MR.
One thing I'm wondering about though is form classes that are never going to be associated with a route, like SearchBlockForm - should we open an issue to move those out of the
Formnamespace? Otherwise they'll be checked for attributes forever. More or less same thing as #3490484: [meta] Lots of non-plugin PHP classes in plugin directories.Comment #25
godotislateI guess we could move SearchBlockForm, but it might be unintuitive for it not to be in the Form namespace.
Alternatively, once we have #3582628: Add a generic way of scanning classes for attributes to scan for the attribute name, it should be skipped from being reflected, based on the
str_containscheck for the attribute.Comment #26
longwaveI think it's fine for SearchBlockForm to remain where it is, contrib and custom code will have similar issues where forms exist that don't necessarily want their own route - as per #25 if we can scan for the attribute name without using reflection then maybe we can improve this, but for now I think it's a micro-optimisation that we can safely ignore.
Comment #27
catchYeah I think #3582628: Add a generic way of scanning classes for attributes will make the class locations less important when we can skip autoloading and reflection, and if for some reason we can't use that approach, we can always revisit it again then. But forms that never have a route associated with them isn't overly common anyway.
Comment #28
catchCommitted/pushed to main, thanks!
This has conflicts in deprecation-ignore.txt on 11.x - given it's @godotislate and @longwave's work I think it would be fine for them to cherry-pick directly and fix the conflicts locally, I'm out of time at the moment to look closer.
Comment #30
godotislate11.x MR: https://git.drupalcode.org/project/drupal/-/merge_requests/16185
Comment #34
godotislateCommitted 5bc84e8 and pushed to 11.x. Thanks!