The most powerful tool in twig

Learn how to make macros return values to communicate information up a tree as we attempt to make Twig into a functional language.

I’ve been working a lot with Craft CMS which uses PHP in its backend and Twig for its templates. The beauty of Craft is that you rarely have to actually write a single line of PHP code even for the most complex logic thanks to everything Craft comes out of the box with.


When I see this:


-in my Craft Project, I am VERY happy. The more uniformity my codebase has, the more efficient me and my team can work.


One thing in the way of obtaining repositories like this — for me — has always been the lack of function which return values. Especially as someone who has primarily worked full stack, I feel extremely limited if I can’t at least pretend I’m adhering to functional programming.


For this reason I sought for (-and found!) a way to implement functions which return values.


Macros

In twig, the closest thing to what I’m looking for is a macro.
Which might look something like this:

{% macro hello_world(input) %}
  {% set hello = "Hello "%}
  {{ hello ~ input ~ "!" }}
{% endmacro %}

{{ _self.hello_world("World") }}


There is no syntax to return a value or to update a variable outside of the macro. You can only put values into a macro and have it print to the document.


There are ways to utilize this behavior to mimic a return function. In Twig there is an extremely powerful syntax for updating a variable.

{% set myText -%}
    {{- _self.hello_world("World") -}}
{%- endset %}

{{ myText ~ " And goodbye..." }}


WOW! Now that is a game changer. Twig DOES support return functions… -it just only supports returning strings. This is enough for us to work with though. The power of this becomes obvious once you see the following implementation:

{% macro add(a, b) -%}
  {{- a + b -}}
{%- endmacro %}

{# imagine the following variables are provided through query parameters or something #}
{% set a = 5 %}
{% set b = 3 %}

{% set result -%}
    {{- _self.add(a, b) -}}
{%- endset %}
{% set result = result | integer %}

{{ _self.add(result, b) }}
{# the output here would be "11" #}


That’s one less JavaScript embed or PHP Controller we have to write! Neat!

Use cases

“What’s so powerful about this?” In my particular use case I got functionality out of this which in JavaScript would not be possible, and in PHP would just be painfully more verbose to implement.


So to simplify my use case lets say I have a tree object (represented in json):

{
  "tree": {

  }
}


a tree can have branches

{
  "tree": {
    "branches": [{
      "leaves": 5,
      "branches": []
    }]
  }
}


Branches can have branches which can have branches which-… Okay cool we’ve modelled out nested objects. What about it? For my use case I had some nested objects (branches) and needed to find out the total number of “leaves” within this complex object. Without return functions this would be impossible, but with our customized return macros we explored earlier, this problem does not require switching to another programming language.


To visualize how complex this problem actually is, let’s visualize our json model to an image


Here is how we get all the leaves using nothing but twig

{# where we get the tree from doesn't matter now #}
{% set tree   = ... %}
{% set leaves = _self.leaf_count(tree) | integer %}

{# return type: integer #}
{% macro leaf_count(branch) -%}
  {%- set nestedLeaves = 0 -%}
  {%- for nestedBranch in branch.branches -%}
    {%- set nestedLeaves_ -%}
        {{- _self.leaf_count(nestedBranch) -}}
    {%- endset %}
    {%- set nestedLeaves = nestedLeaves + (nestedLeaves_ | integer) + nestedBranch.leaves -%}
  {%- endfor -%}

  {%- set leaves = branch.leaves + nestedLeaves -%}
  {{ leaves }}
{%- endmacro %}


Here we see a level of recursion you would not think of implementing when you first pick up twig. You don’t even have to implement maths when doing something like this. Since we’re always printing strings, we could even print a character for each leaf and the apply a | length filter to count them all up. The main point is that we can have macros communicate values UP a tree — not just down!


When we think of the print tags {{ value }} in Twig as a return value statement in other programming languages, Twig suddenly becomes way more than just a simple templating language. Thanks to return macros, there is no limit to how complex we make the models we want to interact with.


With two software development projects already procured, the focus for now will be to discover the world of ecommerce. There's already some interesting products on our radar which will be thoroughly explored! Stay tuned for more news.



Besides software development for clients, we are also looking to plan our own web platform soon to manage authentication and monetization for all our future in-house software products. This will allow Yriclium to focus on what is truly important in a software product without having to constantly reinvent the wheel.



There is a lot to look forward to.

logo animation