Twig Template: Common mistakes
Templates must be simple and easy to read
I saw many and many lines of twig code and still same problems. I introduce several smells you should avoid if you want to have your templates clean and easy readable.
Checking if variable is an array (iterable)
Checking variable using iterable test is probably one of the worse twig constructions I ever seen. Why is it so? Trust me, there is no reason for checking if some variable is iterable
. In my whole carrier I never had a need for this testing. If you use iterable test, than you have problem elsewhere in you code.
{% if users is iterable %}
{% for user in users %}
Hello {{ user }}!<br>
{% endfor %}
{% endif %}
If you use something like above: Maybe test is absolutely useless, you can delete it and nothing happens no matter what came to template. Or maybe your model behaves strange and in case when it should returns an empty array, it returns null
or something similar (0
, ''
) which is not iterable.
You have full control1 what variables are passed to twig template therefore it's your own fault and redundant complication to have one variable which can behave both like array or scalar. When something can be array or iterable object pass always array to twig template and adapt twig source code to handle interable object only. Where does variable $users
came from? Probably from some model class and there is the most appropriate place to add 2 or 3 lines of code to handle uncommon situations like "no user in DB yet". You handle this in one place and in right place, not in every fucking twig template file.
{% for user in users %}
Hello {{ user }}!<br>
{% endfor %}
This code is fully working for zero count of elements, one element or what the fuck ever elements are in array/iterable object.
Main advantage is that you code is much more readable. Other advantage can be in faster rendering because according to benchmark2 positive checking for is_array
is twice slow compared to isset
.
Not using "else" in loop
PHP have no such control structure but in twig - you can useelse
together with loop3. It can be hard from begining to acquire a skill to using for-else-endfor
because when you know several programming languages from c-family it rarely cames on you mind. Think different and master your template:
{% if users is not empty %}
{% for user in users %}
Hello {{ user }}!<br>
{% endfor %}
{% else %}
No user here, sorry.
{% endif %}
Code below is better, isn't it?
{% for user in users %}
Hello {{ user }}!<br>
{% else %}
No user here, sorry.
{% endfor %}
Checking if variable is defined
Using defined test is similar like iterable
, but there are situations where is right to use defined
test. My experience is that defined
is easily abused and raped. I'm going to repeat myself but it's you who have full control what is passed to template. If use pass $article
to template, you can rely, it will be there.
// controller:
return $this->render('MatokBlog::template.html.twig',
array(
'article' => $article,
)
);
{# twig template #}
{% if article is defined %}
{{ article }}
{% endif %}
This is stupid, isn't it? How for god sake this cannot be defined?
Defined equivalent in PHP
This is important: there is no equivalent, because twig renders it into code like this:
if (array_key_exists("article", $context)) {
//
}
It means that defined
only checks if key exists in array. Even null or any empty value is defined according to twig. This isn't same like PHP isset
- just keep this in mind!
There is no way you use defined
like error handling. Of course model can change, controller can change, there are changes in code every day. If mistake was made you can have undefined variable in template that should have been defined. Think about this code:
Dear customer, please send payment for your order to bank account:
{% if bank_account is defined %}
{{ bank_account }}
{% endif %}
This only silence potential error and that's definitely something you don't want to. Letting error to shows in the best you can do... and next good step is write tests for important use cases in your application to avoid "unwanted deletion" some lines from controller.
Correct use of defined
Well, it's hard to say. I don't want you avoid using defined
test in every situation, but template need to be more complex when defined
is used in right place. When I saw defined it stops me for a while and force me to think: What conditions must met to make variable not defined? Does this ever happend?
{#
this is only place (at time I was writing this article) where I use defined in this blog:
There are two DB separates query: 1) articles 2) top images of articles from first query
this means that not every article need to have a top image....
#}
{% if images[article.top_image_id] is defined %}
{# ... #}
{% endif %}
Correct usage of defined
is also when same template is used to render a response from two or more controller. For example Article:show
renders article for user, but AdminArticle:preview
accesible only admin user, shows unfinished article, where isn't necessary to have everything defined same way like in first case.
{% if comment_form is defined %}
{# ... render form ... #}
{# this is correct usage: preview of article doesn't have form to post comment... #}
{# ...or comments can be disabled for given article #}
{% endif %}
Using "is empty" test instead of "default" filter
After everything I wrote above it's priceless to repeat same thing and example must be enough.{% if user.phone is not empty %}
{{ user.phone }}
{% else %}
unknown
{% endif %}
And one fucking line instead of five:
{{ user.phone|default('unknown') }}
Footnotes:
1: Maybe only one reason of using iterable is case when you don't have these control
2: http://www.phpbench.com/
3: Python has for-else also
I'm foreigner. Where I live my friends call me Maťok.