In Part 1, we found out that Varduhi likes cheeseburgers and that declaring variables is awesome. Now that we have some theory (and red meat) under our belts, it's time to apply our knowledge of declarative programming to our Twig templates.

How to Give Your Code a Makeover.

1. Declare your variables once with the default filter

If you're one of those virtuously lazy programmers, you've probably migrated your "global" variables to _layout:

{% set title = entry.title %}
{% set heroImg = entry.heroImg.first() %}
{% set description = entry.metaDescription %}

Of course we know that these variables will work only if entry exists. What if we are on the index page of a blog channel or a manual route with no client-side entry?

Crash.

So let's set these variables only if entry is defined.

{% if entry is defined %}
    {% set title = entry.title %}
    {% set heroImg = entry.heroImg.first() %}
    {% set description = entry.metaDescription %}
{% endif %}

This keeps our page from crashing, but now we must wrap every reference to these variables in if conditionals, such as:

  1. <title>{{ title }}</title>
  2. <div style="background-image:url('{{ heroImg.getUrl() }}')"></div>
  3. <meta name="description" content="{{ description }}">

In fact, our three variables should have fallbacks. Let's add them in:

{% if entry is defined %}
    {% set title = entry.title %}
    {% set heroImg = entry.heroImg.first() %}
    {% set description = entry.metaDescription %}
{% else %}
    {% set title = siteName %}
    {% set heroImg = global.heroImg.first() %}
    {% set description = global.metaDescription %}
{% endif %}

Not too shabby—we've given precedence to entry while providing defaults if entry doesn't exist. Now, what if we want to give our sub-templates the final say over these variables?

{# staff entry #}
    {% set title = "Meet " ~ entry.firstName %}
    {% set heroImg = entry.profileImg.first() %}
    {% set description = entry.bio | slice(0,160) %}

_layout needs to ask whether these variables have already been defined:

{# _layout #}
{% if entry is defined %}
    {% if title is not defined %}
        {% set title = entry.title %}
    {% endif %}
    {% if heroImg is not defined %}
        {% set heroImg = entry.heroImg.first() %}
    {% endif %}
    {% if description is not defined %}
        {% set description = entry.metaDescription %}
    {% endif %}
{% else %}
    {% if title is not defined %}
        {% set title = siteName %}
    {% endif %}
    {% if heroImg is not defined %}
        {% set heroImg = global.heroImg.first() %}
    {% endif %}
    {% if description is not defined %}
        {% set description = global.metaDescription %}
    {% endif %}
{% endif %}

What just happened? We added one edge case and it doubled our code. By asking nicely, we've forced ourselves to ask each variable in each environment the same question: whether or not that variable name has been already defined. We could shorten the control flow with ternary operators, but without an alternative to asking, our code will continue to bloom into an unsustainable mess.

Declaration to the rescue! Let's rewrite our code by setting these variables only once, using the default filter.

{% set title = title | default(entry.title | default(siteName)) %}
{% set heroImg = heroImg | default(entry.heroImg.first() | default(global.heroImg.first()) %}
{% set description = description | default(entry.metaDescription | default(global.metaDescription)) %}

default looks like a filter, but it acts like an operator. In fact, it is the only native Twig "operator" that tests for all three types of existence, which means (1) your code won't crash and (2) only actual content will populate your template.

To further flatten the control flow, we can set extra variables above in a gather-then-assemble fashion:

{% set defImg = global.heroImg.first() %}
{% set defDescription = global.metaDescription %}
{% if entry is defined %}
    {% set entryTitle = entry.title %}
    {% set entryImg = entry.heroImg.first() %}
    {% set entryDescription = entry.metaDescription %}
{% endif %}
{% set title = title | default(entryTitle | default(siteName)) %}
{% set heroImg = heroImg | default(entryImg | default(defImg)) %}
{% set description = description | default(entryDescription | default(defDescription)) %}

Depending on the length of your default chain and whether you will reuse extra variables like defImg, you may prefer the shorter version.

TL;DR: Set your variables once and unconditionally. The reason we set variables is to make our coding lives easier, and wrapping every variable reference defeats the purpose. If there is a chance a variable might not exist, set null as a fallback value:

{% set mightNotExistVar = entry.var | default(null) %}

2. Require your fields

Declaration is great, but without dependable content it's like promising sunny skies and no rain in Mobile. For instance, the examples above assumed defImg and defDescription contained content; if we don't require those fields, we might as well not create them in the first place.

Every entry has minimum requirements. If we're creating a property listing entry for a realty site, the address should be a required field so our snazzy searchbars and Google maps work.

Requiring fields automagically stabilizes your site by:

  1. Coaching your client through a minimum viable entry.
  2. Empowering you the developer to populate content with confidence.

We can make defImg and defDescription dependable by creating a global in Craft called config with an asset field and a 160-character plain text field. Then in our global's field layout, we can require those fields (Settings > Globals > Config > Field Layout > field > "Make required").

Now we can be 100% sure that these variables contain content! The default filter and required fields make an awesome combo, don't they?

TL;DR: Coach your client through a minimum viable entry by requiring the fields your template needs to be dependable.

That's it for this week—stay tuned for three more ways to take the question marks out of your Twig code with declaration!