Explicitly register template_preprocess callbacks in hook_theme()

Created on 21 January 2025, 19 days ago

Problem/Motivation

template_preprocess... functions are very unusual. They are the only thing in modules that's not prefixed by the module.

πŸ“Œ Handle module preprocess functions Active 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: πŸ“Œ Move template_preprocess, _template_preprocess_default_variables into services Needs work

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

πŸ“Œ Task
Status

Postponed

Version

11.1 πŸ”₯

Component

theme system

Created by

πŸ‡¨πŸ‡­Switzerland berdir Switzerland

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @berdir
  • Pipeline finished with Success
    19 days ago
    Total: 382s
    #402071
  • πŸ‡¨πŸ‡­Switzerland berdir Switzerland

    Created 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.

  • πŸ‡³πŸ‡ΏNew Zealand quietone
  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    We discussed this briefly on slack, so I'd like to move some thoughts here.

    #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.

    I'd disagree with the characterization that it is struggling, it is solved. 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

    use\Drupal\Core\Utility\CallableResolver to also support services.

    I think we'd definitely want to do this, 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.

  • πŸ‡¨πŸ‡­Switzerland berdir Switzerland

    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.

    Yes, 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.

    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.

    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 πŸ“Œ Handle module preprocess functions Active 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, πŸ“Œ Move template_preprocess, _template_preprocess_default_variables into services Needs work 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.

  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    As discussed, the question is whether we want to/need to maintain BC

    Yep as I mentioned I don't think it is a blocker for this if that's the direction we go.

    I really think those 3 issues should be kept separate.

    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.

    I'm always surprised by just how opposite we sometimes think ;)

    It's pretty astounding, but that issue is better for it.

  • πŸ‡¨πŸ‡­Switzerland berdir Switzerland

    I pushed an update to use the CallableResolver service, so this supports services now, converted template_preprocess_block() as an example.

  • Pipeline finished with Failed
    7 days ago
    Total: 139s
    #412455
  • Pipeline finished with Success
    7 days ago
    Total: 4855s
    #412490
  • πŸ‡¨πŸ‡­Switzerland berdir Switzerland

    The blocker is in, rebased.

    This has

  • Pipeline finished with Success
    6 days ago
    Total: 2935s
    #413893
  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    Agree that this makes sense

    I 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.

    Agree on the specific array key

    I think it should be preprocess initial to match the other preprocess keys.

    Documentation updates and change record

    I'll create a quick CR.

    Follow-ups to convert remaining template_preprocess, possibly a meta with a few child issues and then a deprecation

    This can be done once this gets a closer review.

  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    I created the follow up πŸ“Œ [pp-1] Convert Template Preprocess hooks to OOP equivalent Postponed: needs info since we need it whether we do this approach or the other.

  • πŸ‡¨πŸ‡­Switzerland berdir Switzerland

    One 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 πŸ“Œ Handle module preprocess functions Active 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.

  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    Putting all the template_preprocesss_* callbacks of core include files into ThemeCommonElements wouldn't be a good fit

    also

    Should module template_preprocess callbacks be in the Hooks namespace

    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.

    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.

Production build 0.71.5 2024