Twig
Twig copied to clipboard
Add support for the ?= operator
I have a lot of templates that are intended to be used with {% include ... with { } %} or {% embed ... with { } %}. Conceptually, I like this a lot. It feels a lot like components in frontend frameworks or function calls.
But there are two things that bother me with how this currently works:
-
It's quite difficult to find out which variables are expected by a template. Currently I'd need to look through the entire template to find, which variables are required, which are optional, and which aren't used at all.
-
It's a little too complex to set a default value for an argument. Since the
defaultfilter isn't suited for this use-case I need to set default values like this:
{% set someParam = someParam is defined ? someParam : 'default value' %}
I had the idea to write my own param filter or function but that doesn't work either because the template crashes if a not defined variable is used with a custom function or filter (I guess this is caused by the strict_variables option, right?). I looked into how default deals with this. But its implementation looks quite advanced.
I think both points mentioned above could be solved at one go. But there are a lot of ways to tackle this. First of all: Is there any interest in solving these issues? I think they are tightly coupled to how both include and embed work, so I think this could help a lot of people.
One idea I have is the following:
{% params num = 1, str = 'hello', flag = false %}
In this case num should default to 1 only if it's missing from (or basically if it's not defined):
{% include ... with { ...no num here... } %}
My params* tag would basically be a shortcut for multiple set tags like the one above. This means it not only works in included or embedded templates but basically everywhere.
Now for point one (i.e. for documentation purposes) I'd also like the following to be valid:
{% params foo %}
On one hand this sends a signal to the user that there's a (potentially non-optional) variable foo required to render this template. But this tag like shown above would actually be a noop. Unless this defaults a not defined variable to null.
*) Maybe {% define ... %} is an even better name since it might work everywhere and it could set undefined variables to null.
After reading through other issues I think you wouldn't want to implement the possibility to mark a variable as required and throw if it's not defined, right? Otherwise, this is also something that could be covered by this tag.
Here's a more advanced and simplified real-live example (using define instead of params):
{# menu-item.html #}
{% define label = '', icon, link, variant = 'default' %}
{% set labelHtml %}
<div class="variant-{{ variant }}">
{% if icon is not null %}
<img src="{{ icon }}">
{% endif %}
<span>{{ label }}</span>
</div>
{% endset %}
{% if link is not null %}
<a href="{{ link }}">
{{ labelHtml }}
</a>
{% else %}
{{ labelHtml }}
{% endif %}
The define tag above would be equivalent to:
{% set label = label is defined ? label : '' %}
{% set icon = icon is defined ? icon : null %}
{% set link = link is defined ? link : null %}
{% set variant = variant is defined ? variant : 'default' %}
But more realistically icon and link would be missing and the template would check {% if icon is defined %}. Imo that's bad because of point 1 above. And it's not as easy to read what the actual default values are.
to set default values, you can probably use the ?? operator (unless you want to accept null as a non-default value)
Okay, that's a bit better (Although, ?= would be even better). Being not able to accept null might be an issue if "not defined" and null makes a difference. But I'm having trouble finding a use-case.
If we follow that approach, the following would be a good alternative:
{% set label ?= '' %}
{% set icon ?= null %}
{% set link ?= null %}
{% set variant ?= 'default' %}
I see you can combine multiple set into one, but I don't like this at all:
{% set label, icon, link, variant ?= '', null, null, 'default' %}
The more variables you define the harder it gets to read. If the following was possible it would be a good alternative to the entire proposed define/params tag:
{% set label ?= '', icon ?= null, link ?= null, variant ?= 'default' %}
The only downside is that you have to assign null as a default value explicitly. But I'd be ok with that.