Problem/Motivation

It is difficult to expect inside the template, which variables are available there, and what they are used for. Currently, in many use cases we only pass render element to the theme system pipeline, and generate the render element into variables inside preprocess functions. Because of this, variables passed to templates are not being documented.

By forcing specifying the variables to theme registry, it would be easy to tell which variables are being passed to the template.

Also a there would be a small extra benefit of doing this, since we could provide a function inside Twig that will return all available variables without printing their contents. This information is already available in the preprocess functions but isn't trustful since any preprocess function could add new variables.

Proposed resolution

Force all hook theme implementations to define variables into the theme registry by running array_filter before passing variables to templates.

Remaining tasks

User interface changes

API changes

Data model changes

Comments

lauriii created an issue. See original summary.

RainbowArray’s picture

I saw a comment on another issue that mentioned something about not adding variables to templates in preprocess. That's a very common task, so maybe I misunderstood that. Would there be a way to add additional variables to the registry in preprocess?

lauriii’s picture

@mdrummond it is common to work that way, but it is not the way theme system should be used. This issue is about enforcing this best practice. It would be a tremendous TX improvement.

Anyone who is still doing this in preprocess, should use render elements for generating variables. Theme system is in place just to allow last minute preprocessing on theme. In most cases, you shouldn't preprocess something you are creating yourself.

If you want to add variables from preprocess, you can use hook_theme_registry_alter to introduce them for the theme registry.

RainbowArray’s picture

I can definitely see encouraging or requiring that the module that defines a particular theme hook should specify the variables it needs for the template either in the the info.yml hook_theme equivalent or in a render element. I guess I'm more talking about themes, particular themes for a client site, that are working with a pre-existing theme hook. Preprocess is very often used to add in some additional variables for a special case that wasn't envisioned in the original theme hook. To me, the point of preprocess functions for themes is to modify the values of existing variables in a theme hook or to add new ones. It's fairly common to do the same thing in custom modules. I don't think either of those is a bad practice that should be discouraged. If it needs to be done in a slightly different way, so that it's easier to understand what variables are defined for a particular template in a particular situation, I think that's fine. I just want to make sure we preserve the basic ability to modify and add to template variables through the theme system.

cyb_tachyon’s picture

laurii said:

Theme system is in place just to allow last minute preprocessing on theme. In most cases, you shouldn't preprocess something you are creating yourself.

I agree - but it is important to note that this wouldn't just be a small change - the scope would be massive. I can think of a few core modules off the top of my head that would need to be redone, for example the responsive image module which spends its preprocess cycles (incorrectly IMHO) loading image styles and tossing in huge multi-tiered arrays of responsive image variables. Not to mention the node module etc.

For the responsive image module, to me it would be much better to relegate most of that functionality to the Twig templates themselves, with the exception of loading the Image style which should happen before rendering anyways. I think this could be applied to other core modules as well.

mdrummond said:

To me, the point of preprocess functions for themes is to modify the values of existing variables in a theme hook or to add new ones. It's fairly common to do the same thing in custom modules. I don't think either of those is a bad practice that should be discouraged.

I respectfully disagree - adding variables sounds like bad practice to me because it introduces yet another source of theme context. In my experience, as a developer you want a "single source of truth" as much as possible. In this case, it would be the theme hook. I think in a perfect world scenario, keeping the preprocess hooks as "the method" for modifying output variables with functions not readily available in Twig wouldn't be horrible, even if I dislike it.

lauriii’s picture

@cyb.tachyon I agree with your every word.

Another good example of a peculiar way to use theme system is what Contextual module does. However, this type of uses we could convert to be done other ways. Not only because of the sake of moving them elsewhere but because it would most likely make that module less fragile because it wouldn't require everyone to remember that contextual module attaches some markup into the title_suffix variable.

If the issue couldn't be fixed that way, for me it would anyway make sense that contextual module adds a new variable for the hook_theme called contextual_links that you would then have to manually print in the template. At least then you'd know by the name of the variable what you'll end up breaking if you remove it.

RainbowArray’s picture

I think having contextual module add a contextual_links variable would be a lot better than sticking a variable in title_suffix, particularly since Contextual is a default, core module. For optional modules, or contrib modules, that's not really an option as a way to affect a template, unless you ask people to manually modify their templates to include the variable, which would be hard to rely on.

I get the theoretical point here, that it would be nice to have a handy place to look to see all the variables. I don't think preventing addition of variables is the only way to achieve that goal. If there needs to be a standardized way to add a variable to the registry for a theme hook, or something like that, I could see that working. But adding variables in preprocess for use in templates is incredibly common and very useful. It's not a bad practice, it's a good one. Preprocess logic is great for manipulating data. Template logic is great for working with classes, looping through variables, deciding whether to show a certain block of markup or not. But manipulating data in templates can run into a lot of problems.

Core itself does a ton of adding variables through preprocess, contrib does too, and it's very, very, very common on every client project I have ever worked on. Changing that would be really disruptive to how Drupal theming currently works, and quite frankly I think it would make Drupal a lot less extensible.

I don't want to have to register a new theme hook for node_that_has_one_extra_variable, figure out how to pass all the data from a node render array into a new render array with the new theme hook, just so I can add an extra variable to my node template.

So I think we really need to look at alternative options to solve the good goal of making it easier to see what variables you can use in a template.

lauriii’s picture

But adding variables in preprocess for use in templates is incredibly common and very useful. It's not a bad practice, it's a good one.

I obviously quite strongly disagree with this. The reason why I think so is because we are making unnecessary expectations in the preprocess function and therefore make theme implementations less re-usable. Let's say, to render an entity, people would pass an entity for a theme implementation and then in preprocess bake the entity into variables. What if one wants to render another type of entity which has a different set of fields, but same design? You'd have to add specific preprocess functions based on a theme suggestion and because of that, only limited use cases are supported.

I approach this problem in following way: it is themers job to define what goes into the theme implementation. It is backend developers job to pass data for that theme implementation. Depending on the complexity the backend developer will create render element for the theme hook to generate the variable or not.

What you are suggesting sounds like it is backend developers job to pass whatever data for the theme implementation, and then themer will bake that into variables using PHP which many themers don't know how to write (not even considering if they want to do so).

People can already work using this kind of workflow, but core doesn't courage at all for doing this. That is what I'd like to achieve here.

Obviously doing something like this would be very disruptive for the way we've used to theme Drupal, but I think it would be a change towards better TX.

I don't want to have to register a new theme hook for node_that_has_one_extra_variable, figure out how to pass all the data from a node render array into a new render array with the new theme hook, just so I can add an extra variable to my node template.

You don't need to register a new theme hook for adding one extra variable. You can alter existing one. I would discourage people from doing this but there is some valid use cases for this.

carwin’s picture

I don't think this sounds like an actual problem that needs to be solved. You're suggesting that we take away something many people find useful and that many projects use for very little real benefit as far as I can tell.

RainbowArray’s picture

The vast majority of theme hooks most themers interact with are created from core or contributed modules. These theme hooks are deeply baked into how Drupal works, and what most themers do is work with the existing templates used to output the data from render arrays implemented in core and contrib to pass data through theme hooks and their associated preprocess functions to templates. There may be a very, very, very small minority of themers who create their own theme hooks, but that's small indeed. Yes, theme hooks are defined by backend developers with a certain set of generic variables that are likely to be useful in templates. Themers come in, and we find no, there's an additional variable I need. Not an existing variable that can be modified, but a brand-spanking new variable. There's often complicated data processing that goes on to get the values for this variable. It's also not uncommon on projects for back-end devs to help out front-end devs by making those new variables in a custom module through preprocess.

I can count on one hand the number of times I've actually created a new theme hook where I had complete control of the variables that I needed for a template. And when I have, it's been in a module, not a theme. Do some people create theme hooks in themes? Maybe? But I'd wager that very, very, very few people do that. That's just not a common thing.

It's hard to overstate how big a disruption this would be to the everyday theming workflow for me and a vast number of other themers.

The goal is good! Make it easier to know what variables you can use in a template. That's cool! But there are other ways to do that than just locking down the addition of variables to templates.

yareckon’s picture

I'm not going to write a long reply about coding philosophy, but just note that in practice adding variables in preprocess is extremely common and useful. Would be sad to see that taken away or made significantly more difficult.

I have read the whole thread, and am still fuzzy on the *practical benefits* should this change indeed be made -- that's not to say they don't exist, just to say that they aren't spelled out well enough, or don't amount to enough that I am able to re-articulate them.

yareckon’s picture

Oh and if you want themers / frontenders to not to have to deal with render arrays, that's IS a good goal! Lemme tell you, backend developers don't want to deal with them either :P Wish we could just cut them out altogether !!

lauriii’s picture

This is about more than just documenting which variables are available in the template. It is about making theme implementation re-usable so that they are not tied to a specific type of data.

I created a repository which demonstrates how we could easily print user and node type of data using the same template if the theme implementation doesn't have specific logic related to the data type. It also shows the workflows that we would have to deal with right now. Obviously, we would have to think about the toolset more if we decide to do this.

Probably there is more than one way to achieve this, so feel free to propose your ideas!

rudiedirkx’s picture

If you want themers so keep using Drupal, don't remove this. It seems like we're making many things way more difficult for corectness. Make Drupal for developers, not for winning a design prize.

DamienMcKenna’s picture

The demo repository uses a lot of code, so I presume there would have to be extra APIs added to make this less burdensome.

I guess it depends how much you want to push the abstraction layers and reusability of smaller and smaller pieces, and adding more logic to one place (code to simplify this architecture) instead of having it somewhere else (preprocess functions inserting variables)..

There's nothing stopping someone from implementing what you're mentioning today using the current APIs, maybe you should propose it as a theming workflow for others to use?

lauriii’s picture

@rudiedirkx I'm sorry if my intention was not clear enough, but I'm not trying to make this change just for the sake of re-architecting Drupal. The reason why I'm proposing this is because I believe it would force us for better conventions. The biggest improvement would bet that Drupal would be more approachable for designers. They don't want to think about Drupal's data structure but instead what they see on the UI. They think about components and how things could be re-used between pages. Currently, Drupal's markup is not re-usable across different data types.

Also, as a result of these changes, I hope we would have a less complicated system than what we have now.

@DamienMcKenna Currently, it seems like that big part of the problems we have with the system are caused by the fact how we use it. Theme system is flexible and allow people to use it in any way they want to. Drupal core itself uses theme system in ways that it shouldn't be used. We have whole parts of a system that are very difficult to theme because of the way they use theme system (Form API is probably the most severe).

It is quite a big chunk of work to write a whole system like this, so it is easier to make small improvements and try to make the TX better that way. For that reason, currently, the two main categories of issues for theme system are about:
A: Setting and enforcing best practices (includes making Drupal core follow best practices)
B: Making sure that there are tools in place to make things happen easily

At this point, I think it would be most beneficial to hear what are the most common scenarios where you end up adding variables in preprocess functions? That would help me better understand your point of view and would allow me to think of what would be the common ground.

TwoD’s picture

FYI: Display Suite's PPF fields heavily rely on preprocessors to fill in their values. I suppose DS could still declare them by altering the theme registry though.

Just a thought, but maybe it'd be possible to run a step after preprocessors and before going into the template which "simply" checks which variables have been passed in and adds them to some lookup table if they weren't already declared? Could perhaps be used to differentiate between 'static' and 'dynamic' theme variables, if that's useful.

Disclaimer: I don't yet know how Drupal actually passes its variable to Twig, so maybe this is impractical for some reason.

RainbowArray’s picture

I still need to look at the example lauriii set up, but wanted to mention a brief thought I had.

Part of the challenge with the process of getting variables from theme hooks through preprocess to templates, is that we conflate inputs and outputs.

The variables specified in a theme hook at least somewhat act as "Hey, here are the things you need to pass into a render array with this theme hook in order for it to work." Sometimes a preprocess function can add another variable that is actually required in order for that theme hook to work, but it's very hard to track that down.

Sometimes these input variables are just used as is and output in a template. But sometimes there needs to be some massaging of that input data before it's useful for it to appear in a template. That input data can be turned into new variables that are easier to work with in the template. For example, an object like a node or a field might be passed into a render array, and preprocess takes care of turning that into something easier to output for a template.

What could be useful is making this separation of concerns more clear. Defining the inputs for a render array—these are the required or the optional items to pass into a render array—and a separate list of the output variables that should be used in the template itself. Not that it should be impossible to access that raw, input data. But having both of those clearly defined might make variables in templates a lot easier to work with. Right now they're all mixed together, and it's confusing.

Some sort of render array registry to be able to look up the inputs and outputs for a render array could be handy. Keep in mind that could vary based on conditional items. It's pretty common in preprocess to only add certain variables under certain conditions, and that doesn't necessarily line up with a template suggestion for example.

It's a complicated system, but that allows us to do some pretty complex things. I'm all for ways to make this easier to work with, I just want to make sure we do so without sacrificing the flexibility we have that enables us to do some pretty ambitious things with Drupal theming.

lauriii’s picture

But sometimes there needs to be some massaging of that input data before it's useful for it to appear in a template. That input data can be turned into new variables that are easier to work with in the template. For example, an object like a node or a field might be passed into a render array, and preprocess takes care of turning that into something easier to output for a template.

This is what render elements are designed for. It will massage some raw data into a render array.

Not that it should be impossible to access that raw, input data.

The values given for the render element are also accessible in the theme system.

Some sort of render array registry to be able to look up the inputs and outputs for a render array could be handy.

Theme registry would play this part after we've fixed this issue. It already has theme implementation variables stored there but isn't very usable as is since in many cases theme implementation don't specify their variables at all.

I just want to make sure we do so without sacrificing the flexibility we have that enables us to do some pretty ambitious things with Drupal theming.

As usual, this is about trade-offs. Most likely we will lose a little bit of flexibility in one part of the system but will add a lot of extra flexibility elsewhere. Most important is that all valid use cases are covered.

I just want to mention that I'm using a workflow like this on my own projects and I've been sharing theme implementations across projects since they've become more re-usable. I think we need more testing and thinking than that, but I just want to mention that we shouldn't say no to this just because it would slightly change the way we do things.

RainbowArray’s picture

Took a look at the example repo. Thanks for sharing that, lauriii. That makes things much more clear.

Here's my understanding of what's being proposed.

Essentially, use render elements for everything rather than simple arrays. If there are variations on the variables used by a render array, manually create a theme hook suggestion that defines those additional variables. This could in theory require a lot more templates, one for each additional optional variable. The decision of whether to use a theme suggestion rather than the default theme hook would took place in a pre-render callback.

The advantage of this approach is that all potential variables are defined within a hook_theme definition. That may or may not be easier to understand than reading through preprocess functions to discover additional required render array keys not listed in hook_theme (or a render element's info statement).

The example given looks fairly understandable for when there is one set of optional variables. I wonder, however, how much more complex this would be when there are, say, five sets of optional variables, which certainly can happen. Would additional theme suggestions be necessary for all the possible combinations of optional variable sets? That could escalate very quickly.

I'm unclear on where the exact advantage lies in defining variables only in hook_theme statements. Would there be some additional method of exposing those defined variables, along with all the possible variations for a particular render array? This code is fairly complex, more complex than what most themers have been exposed to through simple preprocess functions. So unless there is some additional method of taking advantage of this change, I'm still hard-pressed to see where the advantage of this is.

I can see now that there's still flexibility in allowing for expanding upon render elements/arrays defined in core or contrib. Letting themes do additional things to render arrays/elements is pretty key.

I'm just not totally clear yet on what this extra complexity buys us by making this a requirement.

Also, if there really was a change to make this the only ways to add variables to a render array, my understanding is that this would need to be a 9.x issue. Removing the ability to add variables in preprocess would be a huge API change.

cyb_tachyon’s picture

Sounds like all the discussion bases have been pretty much covered.

There is one thing that occurred to me that hasn't been discussed yet.

Global Twig Context Variables. There are more than a few modules and pieces of functionality that depend on being able to add variables accessible globally in every twig context.

Currently, you either need to add a preprocess, or do a lot of custom code to make that happen in Drupal 8.

If we were to move forwards with the implementation we're sketching around here, we'd definitely want to add an easy ability for 'global' twig variables to be added to all contexts during rendering, and think about some of the ways that functionality could be both useful and abused.

lauriii’s picture

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

markhalliwell’s picture

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.