Problem/Motivation
template_preprocess... functions are very unusual. They are the only thing in modules that's not prefixed by the module.
#3495943: Handle module preprocess functions as OOP hooks is struggling a bit with that and need to introduce the ability for modules to register them as hooks but not for themself but a "template" pseudo-module.
The reason they are important is order of preprocess callbacks. Currently, first is template_preprocess(), followed by the template_preprocess function for the specific theme definition/hook if it exists, then global regular preprocess functions such as contextual_preprocess() and finally regular specific module and then theme preprocess functions.
Steps to reproduce
Proposed resolution
I'm proposing a different approach, that instead of magically named callbacks with a special prefix, or OOP hooks registered for template, we put them explicitly into hook_theme as a callback. Similar to #pre_render, #process and the other callbacks you can register in an Element plugin.
A cleaner option would to make a theme definition/hook an actual class like a plugin an then it could have a preprocess() method. There are old issues for introducing that, but it's a far more complex step that requires bigger changes and performance testing.
This requires template_preprocess() to be removed first: #2340341: Move template_preprocess, _template_preprocess_default_variables into services
Remaining tasks
* Agree that this makes sense.
* Agree on the specific array key
* documentation updates and change record
* Follow-ups to convert remaining template_preprocess, possibly a meta with a few child issues and then a deprecation.
User interface changes
Introduced terminology
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #27 | 3501136-nr-bot.txt | 8 KB | needs-review-queue-bot |
| #15 | 3501136-nr-bot.txt | 91 bytes | needs-review-queue-bot |
Issue fork drupal-3501136
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:
- 3501136-explicitly-register-templatepreprocess
changes, plain diff MR !10970
Comments
Comment #3
berdirCreated a merge request with a proof of concept, basic right now, but already covers BC by only looking for and registering template_ prefixed functions if initial preprocess is not defined.
MR is based on the referenced issue, only relevant changes is the currently last commit: https://git.drupalcode.org/project/drupal/-/commit/a955017c8e669d28297af....
Array key "initial preprocess" obviously up for discussion, I picked this to explain the difference to regular preprocess functions.
I copy pasted the invocation, we can for example use\Drupal\Core\Utility\CallableResolver to also support services.
Comment #4
quietone commentedComment #5
nicxvan commentedWe discussed this briefly on slack, so I'd like to move some thoughts here.
I'd disagree with the characterization that it is struggling, it is solved one way in that issue, this issue is an alternative solution.
I'm not sure I'd call it a pseudo module either, it's a prefix, it's basically the same thing Registry does for it.
As for this issue:
I think it's an interesting approach
If this solution is the direction the community wants to go, then I think we'd definitely want to do the CallableResolver bit too, it would be a shame to have DI for other preprocess hooks but not these ones.
This does make theme_registry_alter harder to maintain BC for contrib which is not a problem for the original issue, but I'm not sure how many people are moving template preprocess around, and I'm not sure this should be a blocker for this approach, just an observation.
Another thing I see is this relies on call_user_func_array for calling the initial preprocess, I am not a fan of that call, the original approach only uses it for fallback for custom callbacks added in theme registry alter.
If it's decided that this is the direction to go, I really think this should be done together with the other issue, this isn't too much more and if we pull out the template work in the other issue the changes are likely a wash complexity-wise.
Comment #6
berdirYes, it does. As discussed, the question is whether we want to/need to maintain BC. I'm arguing we don't. That we provide an alter hook for something doesn't mean that it has full BC (form_alter for example. form structures have zero BC, you can do whatever, but we do not guarantee anything around that. Or we could never improve a UI). But we need a framework manager decision.
I'm always surprised by just how opposite we sometimes think ;)
I really think those 3 issues should be kept separate. Each introduces its own concept. Reviewing and getting approval for changes is usually much harder than writing the code, and reducing scope helps with that. This requires that template_preprocess() is gone but this and #3495943: Handle module preprocess functions as OOP hooks can then be mostly parallel IMHO. If we all can agree on this being the way forward, your issue can be refactored to ignore template_* stuff entirely, #2340341: Move template_preprocess, _template_preprocess_default_variables into services already moves it out of the current $prefixes discovery loop. There will be a few conflict in Registry and ThemeHandler, but I'd expect them to be pretty easy to resolve.
Comment #7
nicxvan commentedYep as I mentioned I don't think it is a blocker for this if that's the direction we go.
I wasn't being super serious, but it would reduce conflicts and the total change isn't too different size wise and let's it be handled at the same time instead of separate preprocess deprecations.
It's pretty astounding, but that issue is certainly better for it.
Comment #8
berdirI pushed an update to use the CallableResolver service, so this supports services now, converted template_preprocess_block() as an example.
Comment #9
berdirThe blocker is in, rebased.
This has a few remaining things (outlined in issue summary), but it needs architecture reviews on the concept and naming.
Comment #10
nicxvan commentedI think this is a nice simplification as long as the core team is ok with BC implications. @catch indicated he was ok as long as it is documented on slack.
I think it should be preprocess initial to match the other preprocess keys.
I'll create a quick CR.
This can be done once this gets a closer review.
Comment #11
nicxvan commentedI created the follow up #3504381: [meta] Convert Template Preprocess hooks to OOP equivalent since we need it whether we do this approach or the other.
Comment #12
berdirOne thing to decide on is where those callbacks should live.
In my issue, as an example, I converted template_preprocess_container() to a static method directly on \Drupal\Core\Theme\ThemeCommonElements where it's also defined, And I converted template_preprocess_block() and put that on next to hook_theme in the block module, as a non-static method, so it's called as a service, but it's not a hook. I'm not sure if that's a good idea or not and what kind of patterns we want to adopt here.
Thoughts:
* As mentioned in the issue summary, there are a few issues that want to either define value objects for theme_hook definitions or convert the whole thing into plugin-like discovery with classes for each. If/Once we have the that, preprocess could just live on that. But no idea if that will ever happen
* Putting all the template_preprocesss_* callbacks of core include files into ThemeCommonElements wouldn't be a good fit. There are 11 in form.inc, they could be in a preprocess service in the form component. theme.inc has 20, from generic and low-level stuff like container, time and page to things that could live in their own component such as field, local task/action, breadcrumb and there's also some special maintenance stuff, that for example calls explicitly into template_preprocess_page()
* Should module template_preprocess callbacks be in the Hooks namespace? It's kind of nice to see them next to hook_theme(), but it's not really a hook, so kind of weird, especially considering all my complaining in #3495943: Handle module preprocess functions as OOP hooks about converting them to real hooks. Most modules only have a few of them, only a few have more than 5, by far most is views with 17. Maybe standardize on a preprocess service per module? And views and some complex cases could have separate ones.
Comment #13
nicxvan commentedalso
Honestly I think we don't have to decide that here, but really what I would probably do is break ThemeCommonElements into mutliple files each with their hook part and then roll them up to ThemeCommonElements so we don't change how that is pulled in. That is how theme_common handled it previously so there is already precedence.
I really think that we just keep the initial preprocess wherever hook_theme is. We have helpers in Hook classes already. We may have some edge cases like views, let's not get bogged down in organization details, we can do a bulk conversion again and then determine organization later.
Comment #14
nicxvan commentedAdding framework manager to get a decision for template_preprocess_HOOK direction. Either what this issue does or what #3495943: Handle module preprocess functions as OOP hooks
Comment #15
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. 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 #16
berdirRebased.
Comment #17
larowlanMy personal preference is the approach here over the pseudo 'template' module in the the other issue. I'll put this to a poll of the other framework managers.
Comment #18
nicxvan commentedLet us know what they say and we can start working towards this if there is agreement!
Comment #19
berdirWorked a bit more on this, introduced a ThemePreprocess service and moved a bunch of template_preprocess() callbacks into that, specifically those that use services, so we can see a bit more how that works with DI.
Doing all here is too much I think, especially since some of them should probably also live in different compoments, for example field templates could move to Drupal\Core\Field, either in a separate service or an existing one if something fits.
Also noticed a node dependency in template_preprocess_page(), that should probably move to node module, no reason for it to be there, but also kind of out of scope.
And, had to shuffle the bubbling stuff around a bit in ThemeManager, noticed while testing the installer that it wasn't called if there was only an initial preprocess and nothing else.
Comment #20
nicxvan commentedOk I looked through your changes, they all make sense to me.
As for the conversion, I think this needs to be done in a couple of stages, I think what you have converted here is great, we probably want to follow a similar tactic to what we did for hooks, let's convert enough in this issue to prove the concept, which I think you've done.
Once this is in we can complete #3504381: [meta] Convert Template Preprocess hooks to OOP equivalent to convert the rest and deprecate, then we can discuss better organization ad nauseam in follow ups. The thing is since these are in initial preprocess changing the locations for organization won't affect reordering.
Comment #21
berdir> let's convert enough in this issue to prove the concept,
Yes, that was the idea, convert a few different examples, including DI. One example more that could be considered would be one in an include file, so we can "prove" that we can remove and deprecate the file key then afterwards. but I think that's pretty obvious?
Remaining steps here:
1. Decide on BC. I removed them for now to make the review easier, but I think we want to keep at least the ones in theme.inc as a BC layer, there are a bunch of calls to them, for example the one in theme.inc about the maintenance (and then the install steps page chained). Not sure about the module ones, but there are far less than module hooks, so doesn't hurt too much to keep them. Plus, the BC layer is implicit and free, if there is an initial preprocess callback defined, we skip template_ discovery.
2. Remove references. Tons of twig files reference their template_preprocess_xy() function. We can either update them or remove them. I'm unsure what the value of that is and it's duplicated a lot and all the copy pasted templates into contrib/custom themes will be out of date now. So future changes might invalidate them again.
3. Change Record/documentation.
Comment #22
berdirOh, i hadn't seen that you already started a change record. I updated and extend it:
* Made it clear that this this is the new format and it replaces template_preprocess() (no "can"). Once this is committed, the old style is essentially deprecated and deprecations will be added once core is converted.
* Switched to block as example, because nobody except core will have to deal with the ThemeCommonElements and theme.inc stuff like that.
* Extended the example to include the hook_theme() definition in both cases
* Explained and linked the CallableResolver and what's supported and what not.
* Added example on how to provide BC with older core versions
* Adapted the breaking changes section, there is only a single template/initial preprocess (for a given theme hook, now that template_preprocess() is deprecated), you can't reorder them. while you could technically move them after other preprocess callbacks, I don't see a scenario for that, that would definitely cause problems. I strongly assume the main use case for that was just explicitly defining it for dynamically defined theme definitions.
Comment #23
nicxvan commentedCR looks much better!
I added a couple of questions on the MR.
I'm wondering at this point if we need to wait for the go ahead, this is looking pretty close from my perspective.
Comment #24
berdirI'd be ok with moving forward. there's still the question about the references in the template files. any preference in regards to updating or removing them? I'll try to ping Theme API maintainers over this issue in general and specifically this.
Comment #25
nicxvan commentedI'd vote remove.
Comment #26
joelpittet@berdir Making the references to the preprocess in the Twig file is important to indicate the alter point.
That said, I prefer using the new/recommended approach while still preserving the old method. This serves as implicit documentation, helping those extending it understand how to do it going forward properly.
So +1 for what you've been doing from what I see at a glance. /Theme system maintainer hat 🎩/
Comment #27
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. 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 #28
berdirI added some docs, changed the theme.preprocess service, restored and deprecated the template_preprocess_HOOK() functions and also updated all @see references in templates for the converted ones. That blows up the diff stats quite a bit (64 files, + 688, − 394), since all the function docs are no longer removed.
Also had to reflow several comments due to the new enforced phpcs rules.
Comment #29
nicxvan commentedOk, we deprecated template_preprocess so it is ok to deprecate here too I think for consistency.
I reviewed this very carefully to ensure everything aligns. I have also reviewed the general functionality several times as you've iterated.
This approach is great!
The only thing I questioned was whether to add any additional comments on theme.api.php
Specifically to explain the static::class shortcut since hooks have to be services, but that might be more confusing since the CallableResolver documentation is available. I lean towards your changes are sufficient.
Also unsure if I need to wait to hear back from other framework managers for #17.
I think this is ready!
Comment #30
berdirPushed some docs improvements, I think that's sufficient? Since hook_theme() docs/example is non-OOP, I can't really put `static::class . ':preprocessList'` in there, but I mentioned in the docs above.
Comment #31
nicxvan commentedI think that covers my concerns!
Comment #32
nicxvan commentedAdding 11.2.0 release priority since the preprocess hook issue now depends on this one.
Comment #33
catchAdded a few minor comments to the MR, nothing big but enough to move back to needs work when combined. Overall this looks really good.
Comment #34
berdirI moved date/time related preprocess to a separate class in the Datetime componen, that only needs the DateFormatter injected. Then we also have an example that we can build on for example for field templates. All other dependencies are for page and html templates, I don't think there's benefit in splitting that up.
Comment #35
nicxvan commentedI think all of Catch's comments have been addressed!
Personally I think leaving the direct call as an example is fine for now. I confirmed the remaining items are all in html and page and agree it wouldn't improve anything by further splitting.
Comment #38
catchAsked @pdureau to take a look at this just to make sure it doesn't conflict with any of his longer term plans for preprocess, both of us think it doesn't conflict at all. Removing the framework manager review tag since both me and @larowlan are +1.
Committed/pushed to 11.x, thanks!
Comment #41
acbramley commentedJust noticed when rebasing #3078334: Datetime and Datelist elements should render as fieldsets that all of the functions called from date related preprocessors are calling BC code incorrectly.
preprocessDatetimeFormdoes not exist on that class, rather it's on\Drupal\Core\Datetime\DatePreprocessWill open an issue.
Comment #42
acbramley commented#3522103: Date based template_preprocess BC code is incorrect