Craft CMS is awesome. It’s approachable, concise, customizable, and powerful. Not to mention pretty! Twig is pretty cool, too, but it can be brutal to noobs.
I’ve been using Twig for only five months, so I’ve been in that boat (and still am!). Maybe my struggles with the yellow screen can inform you on how to avoid the potholes that lead to it.
Var You There?
Most of our 500 errors originate from calling data that doesn't exist.
And asking data if it exists is the hardest thing to do.
Here's why:
Let's call an undefined variable.
{% if undefinedVar %}
<h1>Hello World!</h1>
{% endif %}
{# crashes #}
Now call a null variable or an empty string:
{% if nullVar %}
<h1>Hello World!</h1>
{% endif %}
{# doesn't print #}
{% if emptyString %}
<h1>Hello World!</h1>
{% endif %}
{# doesn't print #}
This is standard behavior among languages, but with a CMS, it can be difficult to predict whether content—and the variable that is set to it—will be empty or nonexistent. Furthermore, what if our variable is an object and we're calling its key? Or a string that is defined but has no length? Or a variable that is set to either null or an empty string? The chances of a sunny screen are high.
Twig gives us three operators to interrogate variables:
{% if var is defined %}
{% if var is not null %}
{% if var is not empty %}
or{% if var | length %}
If we want to cover all the edge cases, our inquisitive code can start looking like this:
{% if var is defined %}
{% if var.img | length %}
{% set img = var.img.first() %}
{% endif %}
{% endif %}
{% if img is defined and not empty %}
<img src="{{ img.getUrl() }}">
{% endif %}
And this:
{% if var is defined and not null and not empty %}
{% if var.img | length %}
{% set img = var.img.first() %}
{% endif %}
{% endif %}
{% if img is defined and not empty %}
<img src="{{ img.getUrl() }}">
{% endif %}
At the end of the day, we still don't know if our content is dependable.
(To understand just how subtle the difference is between undefined, null, and empty strings/arrays, take a look at this chart from Straight Up Craft.)
Pop the Why Stack.
Instead of spending hundreds of lines of code trying to safeguard our sites against the yellow screen, let's take a step back by asking some questions.
What makes a variable useful to our template? Most of the time, we just want to know if there is content where we are looking. We don't really care about the differences between undefinedVar
, nullVar
, and emptyString
—all three values should fail without crashing our site.
Are we creating unnecessary edge cases? if statements are like a rabbit colony: for each question we ask, we have to answer at least two cases.
But here's the central question:
Should we be asking in the first place?
To answer that question, we're going to take the back door of declarative programming.
Liiiiiiiightbulb.
Asking nicely seems to be multiplying our problems, so why ask at all?
Declarative Programming, in contrast to Imperative Programming, is a general philosophy of stating what we want (data), rather than enumerating how to get to it (control flow).
Meet Varduhi, our designer.
Varduhi wants a cheeseburger from FIVE Mobile (a.k.a. best burger in Downtown Mobile).
Being the sociable type, Varduhi asks her coworkers to join her.
VARDUHI (secretly drooling over cheeseburgers from FIVE)
I'm game for lunch. Anyone wanna go? Maybe FIVE or something?
RHEN
I'm meeting with a client at Von's Bistro.
MARCUS
I feel like Von's too. Let's go!
(No one gets cheeseburgers from FIVE)
Notice that Varduhi has nearly guaranteed that she won't get a cheeseburger by stating her preference as a question. How many edge cases exist? As many as restaurants in Downtown Mobile. So the next week, she will rephrase her preference as a statement.
VARDUHI (still drooling over cheeseburgers from FIVE)
I'm going to FIVE for a cheeseburger. Anyone want to join me?
RHEN
I'm meeting with a client at Von's Bistro.
MARCUS
I feel like sushi.
(VARDUHI gets her cheeseburger from FIVE)
Swap var
for Varduhi and content for cheeseburger, and you'll admit the declarative approach is much more tasty.
Here we're asking Varduhi's friends if they exist and have lunch plans:
{% if friends is defined and is not empty %}
{% set lunchPlans = friends.order('RAND()')[0].lunchPlans %}
{% elseif var.lunchPlans is defined and is not empty %}
{% set lunchPlans = var.lunchPlans %}
{% else %}
{% set lunchPlans = 'cheeseburger' %}
{% endif %}
{{ lunchPlans }}
And here, we're seemingly declaring Varduhi's lunch plans into existence while providing a juicy fallback:
{% set lunchPlans = var.lunchPlans | default('cheeseburger') %}
{{ lunchPlans }}
Besides getting Varduhi more cheeseburgers, swapping declarations for if statements will help us:
- Refer to variables with confidence. This means fewer last-minute checks like
{% if var %}{{ var }}{% endif %}
. - Reduce edge cases. This simplifies debugging because we've removed extraneous control flow from the code.
- Document and prioritize how data is attached to variables. Nested if statements are a pain to read, but declarations implicitly outline our priorities as we set variables to content.
Next week, we will apply the declarative paradigm to our Twig code and see what problems it simplifies…and often eliminates.