Problem/Motivation
#3311365: Use PHP attributes for route discovery introduced #[Route] attributes on methods.
Routes that return pages generally need to specify a page title. This is currently done in the defaults._title property:
#[Route(
path: '/admin/config/system/site-information',
name: 'system.site_information_settings',
requirements: [
'_permission' => 'administer site configuration',
],
defaults: ['_title' => new TranslatableMarkup('Basic site settings')],
)]
Steps to reproduce
Proposed resolution
Promote defaults._title to a top level title attribute by extending the Symfony Route attribute class:
#[Route(
path: '/admin/config/system/site-information',
name: 'system.site_information_settings',
title: new TranslatableMarkup('Basic site settings'),
requirements: [
'_permission' => 'administer site configuration',
],
)]
In turn this also lets us use closures in place of separate _title_callback methods:
#[Route(
path: '/block/add/{block_content_type}',
name: 'block_content.add_form',
title: static function (BlockContentTypeInterface $block_content_type) {
return new TranslatableMarkup('Add %type content block', ['%type' => $block_content_type->label()]);
},
requirements: ['_entity_create_access' => 'block_content:{block_content_type}'],
options: ['_admin_route' => TRUE],
)]
Remaining tasks
User interface changes
Introduced terminology
API changes
A new Drupal-specific #[Route] attribute can be used, extending the Symfony one.
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|
Issue fork drupal-3607968
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- hermes/3607968-route-title-11x
changes, plain diff MR !16652
- 3607968-promote-title
changes, plain diff MR !16194
Comments
Comment #2
longwaveComment #4
longwaveFirst pass at this, added the new attribute subclass and promoted the title property in system.module routes.
This MR was assisted by Claude Code.
Comment #5
longwaveComment #6
godotislateAre there any other properties we might want to promote to top level? It'd be better to get them in now.
Like
_title_callback?Comment #7
longwaveNot sure.
_title_callbackhas accompanying properties that aren't as widely used -_title_argumentsand_title_context- so we would likely need to do those as well, and then that feels like a slippery slope. This was intended just as DX convenience for the 90% use case where you are routing a controller or form that has a static title.There might be some more common ones that we should promote? Maybe worth doing an analysis of *.routing.yml?
Comment #8
godotislateAgreed on
_title_callback.One thought, if
_csrf_tokenis going to be required per #3508087: Make it harder to have routes vulnerable to CSRF, that might be a good candidate. OTOH, my own preference would be not making it required, though I don't have any alternative suggestions.Comment #9
longwaveIn core there are 414
defaults._titledefinitions. The only thing there is more of isrequirements._access, which has 509. There are also 179requirements._permission. After that there's a long tail, none more than 100. I think if we convert any more it should only be these three at most.Alternatively, what if we used a separate attribute here, instead of extending Symfony? We could even add other convenience attributes:
Not sure if this is really better DX or not though.
Comment #10
longwaveFWIW Symfony does handle access control with a separate attribute, because it's in a separate package: https://symfony.com/doc/current/security.html
Comment #11
longwave#[PageTitle]could be extended to handle static and dynamic titles from a single attribute:Comment #12
longwaveAlso I was wrong in #7 - title_arguments and title_context are actually for adding args/context to title when it is automatically translated, and we sidestepped that by using
new TranslatableMarkup()directly (although you can't pass dynamic args now...)Comment #13
longwaveAnd because this is no longer stored with the routing data we might even be able to use closures to inline dynamic title code:
Having said that for dynamic titles you can also just return
#titlefrom the controller...Comment #14
godotislateThere are advantages to this approach, and I don't think I mind it. But a similar issue for entity type definitions has had a mixed response: #3488054: Make it possible to define entity types with multiple smaller attributes.
Comment #15
longwaveUpdated IS with the two options - a single extended
#[Route]attribute or a separate#[PageTitle]attribute.Comment #16
mstrelan commentedI think a seperate attribute for PageTitle is awkward. Another option, that might also be awkward, is a title param that takes a PageTitleInterface, which could either be a simple PageTitle, which is essentially just a TranslatableMarkup, or a PageTitleCallback. I haven't really thought this through, but it might be a way to cover several different options without needing to worry about all the permutations at once.
Comment #17
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. The merge request has merge conflicts and cannot be merged. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #18
longwaveAdded support for
titleclosures to the#[Route]attribute and tested it by convertingblock_content.routing.ymlwith a_title_callbackto use an inline closure instead.This is awkward when there is more than one Route attribute on a method, because I'm not sure we know which attribute to look up again, but maybe that's an edge case we don't need to handle.
Comment #19
godotislateThis brings up a problem if we introduce a
#[PageTitle]separate attribute, because if there are more than one Route attributes on a method, which does it target?I think it makes sense to go with the Drupal Route attribute subclass, because the use of
_title_callbackis more of an edge case.Comment #20
longwaveI guess this is the question here: is the page title tied to the page (so there should only be one), or the route?
Comment #21
mstrelan commentedWhen you consider that a route can return a json response or a redirect response it's clear the title doesn't belong to the route. That said, the route should be able to suggest a default title, which is exactly what we already have. Ideally we should return something like a HtmlResponse object that has title as a property so we don't have to rely on this being manually added to a render array.
Comment #22
longwaveWe can support multiple attributes - instead of [class, method] we can store [class, method, index] so we know which attribute has the closure. Added this, and some test coverage.
This now has the same capabilities as the existing YAML route declarations, just with additional syntactic sugar for both static and dynamic titles.
Comment #23
longwaveIf people are happy with the current direction I'll update the IS and write a CR.
Comment #24
godotislateDon't know about "should", but for example in SystemController,
systemAdminMenuBlockPagehas several different titles.Comment #25
longwaveYeah, so that rules out
#[PageTitle]being a separate attribute.Comment #26
longwaveComment #27
godotislateThis is really nice work! I like the closure handling.
A few small comments on the MR, but we're just about there.
I did double check that the skip file logic works for the new attribute, and it does:
Comment #28
longwaveThanks for the review. I accepted the suggestions and wrote a CR: https://www.drupal.org/node/3614321
Comment #29
godotislateI think the MR and CR look good now.
Comment #32
catchYes this looks great. The closure handling is very nice, and I couldn't find anything to complain about.
This is soft-blocking a lot of route conversions from YAML, so let's get it into main and 11.5.x now, doubt we'll find problems to iron out but gives time for that too.
Comment #34
mcdruid commentedLooks like the cherry-pick to 11.x has caused some test problems:
https://www.drupal.org/project/drupal/issues/3614402
Comment #35
mcdruid commentedComment #36
longwaveAs per the above issue, closures in attribute arguments are only allowed in PHP 8.5 and above: https://wiki.php.net/rfc/closures_in_const_expr
We need to revert this from 11.x, not sure whether to just keep it in 12 or disallow closures in 11 or something else.
Comment #38
catchReverted from 11.x
Comment #39
godotislateWhy don't we backport the closure functionality as it is, but remove the test cases. Then document in code and the CR that setting the attribute property value to a closure is only supported for PHP 8.5+?
Comment #40
longwaveWe do have to revert the block_content conversion from 11.x. This also blocks converting the remaining routing.yml in a similar way, but I guess we can just continue that in main only.
We could even keep the test cases and skip on PHP 8.4 and earlier I guess.
Comment #41
quietone commentedThe change record branch/version is 11.5.x/11.5.0 so that may need to be changed.
Comment #44
longwave11.x backport in MR!16652, keeps the closure functionality but limits the tests to run on PHP 8.5+ only. block_content.routing.yml is still converted but uses
_title_callbackas before.Comment #45
longwaveUpdated the CR to mention closures are only available in PHP 8.5.
Comment #46
godotislate11.x MR looks good, just one question about a title callback method we're planning to deprecate.
Comment #47
godotislatelgtm!
Comment #49
catchCommitted/pushed to 11.x, thanks!