Repository navigation
Drupal route names, route parameters and upcasting - #535
Merged
Merged
Conversation
This was referenced Nov 5, 2026
Merged
Closed
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
A route declared by a module's, core's, or a contributed extension's routing file completes, jumps to its declaration, and lists its references in Url::fromRoute(), new Url(), Link::createFromRoute(), a form state's setRedirect(), a controller's or form's redirect(), and the URL generator, access manager, and route provider methods that take a route name. Test modules' routing files count only while open. Assisted-by: claude-code/claude-opus-5-5
The route_name and base_route of a menu, task, action, or contextual link, and the routes an action appears_on, navigate to the route, show up in its references, and complete from the declared routes. A parent link or task is not read as a route. Assisted-by: claude-code/claude-opus-5-5
A #[Route] on a controller under an extension's Controller namespace declares its route the way Drupal's attribute route discovery names it: the class attribute's name prefixes the method routes, an invokable class with no method routes takes the class's own, and a method route without a name declares nothing. path() and url() in a Drupal template complete and navigate route names. Assisted-by: claude-code/claude-opus-5-5
A class whose file uses Symfony's Route or RouteCollection, as route callbacks, route subscribers, and entity route providers do, adds the route that $collection->add() and $routes[...] = new Route() name. A name written out is declared there and navigable; one built from parts is kept as a pattern of the names it can take, for the unknown-route check to come. Assisted-by: claude-code/claude-opus-5-5
In a routing file, the entity type of an _entity_form, _entity_view, or _entity_list and the service of a service:method _controller or _title_callback jump to their declarations and show up in their references. Assisted-by: claude-code/claude-opus-5-5
A route name read in PHP that no routing file, route attribute, or route builder declares is reported. Names nothing can tell missing are left alone: the entity.* and view.* routes entity route providers and Views build, names matching the shape of a route a builder builds from parts, route names in test code, and every name in a project whose routing files were not read. A Class::method route alias is judged by whether the method exists. Assisted-by: claude-code/claude-opus-5-5
The playground's controller redirects and links to its route, a menu link and the greeting template name it too, and the assertions check that every name they use is one the routing file declares. DR5 leaves the backlog; the route parameters it pointed at are filed as their own item. Assisted-by: claude-code/claude-opus-5-5
Route builders in traits, routes held in a variable or returned in an array literal, and builder files that import the routing component as a group are read. A name a builder builds with no written-out text in front, such as "$name.$method", stands for the extension's own routes instead of any name with a dot in it, so the unknown-route check keeps judging every other route. A route on an abstract controller is declared, and a Class::method or Class route name is accepted only when attribute route discovery gives a controller that alias. Assisted-by: claude-code/claude-opus-5-5
…lists
path('<fr in a Drupal template completes <front> and the other angle-bracket routes, and an appears_on list whose items sit at the key's indentation is read and completed.
Assisted-by: claude-code/claude-opus-5-5
A route builder's add() declares a route only when its second argument is a new Route or a variable, so an add() on any other collection in the same file declares no route and does not stand for the extension's routes. Assisted-by: claude-code/claude-opus-5-5
The example code that documents hooks in an extension's *.api.php names invented routes, like test code, so its route names are not reported. Assisted-by: claude-code/claude-opus-5-5
The mixed workspace checks that a Symfony template and Laravel's route() keep naming their own routes, that a Drupal template names Drupal's and still completes an extension's Twig filters, and the receiver unit test covers the route methods Drupal reads. Assisted-by: claude-code/claude-opus-5-5
Each placeholder of a routing file's path or a #[Route] path declares a parameter of its route, and the keys of the parameters Url::fromRoute(), new Url(), Link::createFromRoute(), the redirect and URL generator calls, and a links file's route_parameters pass go to it, find its references, hover, and complete. A parameter no path declares is not reported, since Drupal sends it as the query string. Assisted-by: claude-code/claude-opus-5-5
A parameter of a route's controller or title callback is the entity class when Drupal's entity converter loads it: a type the route's options declare, a parameter named after an entity type and hinted with a class its entity class fits, or the entity type an _entity_view or _entity_form names. An untyped parameter nothing upcasts keeps no type. Assisted-by: claude-code/claude-opus-5-5
A controller or title callback parameter named after an entity type and hinted with a class its entity class is not receives the raw ID, which every route that reaches it passes, so it is reported. Untyped and builtin-typed parameters, and those a route declares in its options, are not. Assisted-by: claude-code/claude-opus-5-5
The keys of the hash a Twig path() or url() passes after a Drupal route name go to the placeholders of that route's path and count among their references. Assisted-by: claude-code/claude-opus-5-5
…changelog The playground's routing file, controllers, links file, and template carry Try: sites for route parameters and an upcast controller parameter, with runtime checks, and DR5 leaves the backlog. Assisted-by: claude-code/claude-opus-5-5
… docblocks Read the routes an entity route provider renames with $routes->add() even when its file never imports Symfony's routing classes, so core's node.add and node.add_page are declared, and count a route returned by a call as added. Keep a declared parameter type that already names a subclass of the upcast entity class, such as a bundle class in a docblock, instead of widening it to the entity class. Sort the template spans of a Drupal project that is not a Twig project. Note in DR20 the route names the shape rules leave unjudged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds DR5: Drupal route names and their parameters work wherever Drupal reads them, and controller parameters get the entity Drupal passes them.
#[Route]attribute or a route builder complete, jump to their declaration and list their references wherever code, a links file or a template names them:Url::fromRoute(),Link::createFromRoute(),$this->redirect(),$form_state->setRedirect(),route_name:in menu, task and action links, and Twig'spath().entity.node.canonical,view.content.page_1) are accepted by their shape, so they never warn; completing them is left for DR20.route_parameters:and bypath()jump to the{placeholder}of the route's path and complete from it._entity_form,_entity_viewand similar defaults name are navigable.Upcasting follows Drupal's rules, not the parameter name alone
The roadmap asks to type an untyped
$nodefrom the route. Drupal itself does not do that:EntityResolverManagerconverts a parameter only when the route declaresoptions.parameters.<name>.type: entity:<type>, uses an_entity_*default, or the controller hints the parameter with the entity class or an interface it implements. An untyped$nodeon a plain route receives the raw ID.So this PR types a parameter as the entity only when the route converts it by those rules. Once it does, every method that receives it gets the entity, including an untyped title callback, as in the example above.
The warning is deliberately narrow: it fires only for a parameter named after an entity type, on a controller method PHPantom fully reads, with a hint unrelated to the entity class, on a route that declares nothing about the parameter. Anything less certain (converters, bundle classes, title callbacks, routes it cannot fully read) stays silent.
On
drupal/redirectanddrupal/flag, the only new report is a real bug:FlagFormBaseredirects toflag_list, which is a form ID, not a route. Peak memory grows about 2% from reading core's routing and links files; time is unchanged.In Zed, the parts that start in a YAML or Twig file need zed-extensions/php#155; other editors already send those files to PHPantom.
Builds on #534.
Checklist
If applicable:
CHANGELOG.mdREADME.md,docs/,examples/)config-schema.json). Not applicable: this PR adds or changes no setting.the changes and why they are needed.
the changelog, and other user-facing content, and removed or corrected
anything inaccurate, unclear, or unnecessary.