Matok's PHP Blog

Twig Template: Common mistakes

article image

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 use else 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',
        '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 is not empty %}
    {{  }}
{% else %}
{% endif %}

And one fucking line instead of five:

{{|default('unknown')  }}


1: Maybe only one reason of using iterable is case when you don't have these control
3: Python has for-else also

If you like this article then mark it as helpful to let others know it's worth to read. Otherwise leave me a feedback/comment and we can talk about it.

I'm foreigner. Where I live my friends call me Maťok.

Comments - Coming Soon