Mohit AgheraSenior Developer
The right way to check for empty content in Twig
Have you ever used {% if content|render|trim is not empty %}
in twig to validate content is not empty? If so, you might end up with some surprises related to the use of the render
method.
Introduction
Checking that content is not empty before rendering is good practice. Typically we call the “trim” method as well so all the space gets trimmed and we get an accurate indication if the content is empty.
Usually, developers uses snippet like {% if content|render|trim is not empty %}
to ensure that content is not empty.
Recently while working on one of our projects, the client informed us that Webform was having issues with Recaptcha and was throwing errors related to invalid tokens. To debug further, we disabled recaptcha handler and saw that Webform submissions were being recorded twice.
While debugging further, we noticed that all the things were working as expected for Webform and simple_recaptcha module’s Webform handler. So both of those causes were out of the picture.
We'd experienced a similar issue before, and in that case the cause was a [node:description]
tag from Metatags that was rendering the embedded Webform. At first we thought this might have been the case, but debugging revealed that Metatags was not in the mix this time.
During debugging further around rendering process, we noticed that render()
was being called twice. Rendering twice was causing issues with recaptcha tokens. Because the second render was invalidating the original recaptcha token.
Upon debugging further we found the following twig templates code was causing issues.
{% if content|render|trim is not empty %}
<div{{ attributes }}>
<div{{ region_attributes.content }}>
{{ content.content }}
</div>
</div>
{% endif %}
Here, if you notice the block {% if content|render|trim is not empty %}
that we are calling render
method of TwigExtension.php
. So this was rendering the content the first time and then the second time we are actually rendering the content using {{ content.content }}
. So this second render was invalidating the previous token generated by the Webform handler.
Solution
As the use of the render
function was causing issues we need to use a different approach to validate the issue.
In such situations twig’s set
tag comes to the rescue.
We can use it in conjunction with the spaceless twig filter to only render the content once. The spaceless filter makes sure we don't get a false positive from whitespace, just like the trim function did before.
So we can refactor above twig snippet using the set
tag and the spaceless
filter like this:
{% set content_render %}{% apply spaceless %}
{{ content.content }}
{% endapply %}{% endset %}
{% if content_render is not empty %}
<div{{ attributes }}>
<div{{ region_attributes.content }}>
{{ content_render }}
</div>
</div>
{% endif %}
By using this approach, we can achieve the same thing as the original approach, but with only one render.
Update
Another approach is to use is directly set
statement. It also gives similar results. Like we discussed earlier, the main gist is to avoid double render which might cause unexpected issues.
{% set content_render = content.content|render %}
{% if content_render|trim %}
{{ content_render }}
{% endif %}
Thanks to Hawkeye Tenderwolf for the suggestion.
Conclusion
The set
tag in conjunction with the spaceless
filter allows us to safely render a variable, remove any whitespace between HTML tags and then use that as a variable to evaluate if the output is empty. If not, we can just output the already-rendered variable and in doing so avoid additional render in our twig templates.