Twig Macros

Macros are to Twig what functions are to PHP. They help the developer reuse certain repeatable elements, and they give an additional clean and organized look to the final code. So here is a quick an easy Macro tutorial for Twig.

Aims of this tutorial :

  • Create a simple twig macro
  • Include it and use it in a template
  • Create a second macro that makes use of the first one

Resources :

Versions :

  • Symfony 3.4 (LTS), but should work with all 3+ versions
  • Twig 2.0

Let’s get started !

Part 1 : Context

Let’s suppose we have an entity that holds phone numbers : Phone (prefix, number, type)

  • prefix : array of numbers and special characters such as ()+, nullable
  • number: array of numbers,  not null
  • type: string, not null, can take two values ‘home’ and ‘cell’

What we want is to have a macro that takes a Phone objects and outputs its content, styled nicely.

Part 2 : Creating the Macro

Macros are written directly into twig files, so I am creating phone_tools.html.twig for this one.

The files containing macros are no different from any other template files. So they can be stored anywhere within Symfony’s Resources folders (either in the app folder or the individual bundles folders). As far as I am concerned, I always place them along side with the other template files of the same entity.

So in this example, I would store my file like this : src/Playground/CookiejarBundle/Resources/views/Phone/phone_tools.html.twig

Macros start with {% macro style_number(phone) %} and end with {% endmacro %}

Macros can receive as many arguments as needed, and all the arguments are always optional.

So here is what our style_phone macro may look like :

{% macro style_phone(phone) %}

    {% if phone is not null %}
    
        <div class="widget yellow-bg">
            <div class="row">
                <div class="col-xs-4">
                    {% if phone.type == 'cell' %}
                        <i class="fa fa-mobile fa-5x"></i>
                    {% else %}
                        <i class="fa fa-phone fa-5x"></i>
                    {% endif %}
                </div>
                <div class="col-xs-8 text-right">
                    {% if phone.type == 'cell' %}
                        <span> Cell </span>
                    {% else %}
                        <span> Home </span>
                    {% endif %}
                    
                    <h2 class="font-bold">
                        {% if phone.prefix is not null %}{{ phone.prefix }}{% endif %} {{ phone.number }}
                    </h2>
                </div>
            </div>
        </div>
        
    {% endif %}
    
{% endmacro %}

Let’s take a closer look :

  • First of all, we need to check that our main argument is not null (line 3)
  • Next we start building our output (lines 5-7)
  • Depending on the type (cell or home), we add a different icon (lines 8-12), and different labels (lines 15-19)
  • The last thing we add is the actual number with or without its prefix (line 22)

Now that our macro is done, we can use it anywhere in the templates to style phone objects.

Part 3 : Using the Macro

In order to use the macro, it first needs to be imported. So let’s suppose we need to use the macro in a template phonebook.html.twig . So we add the following line to the template file :

{% import "@CookiejarBundle/Phone/phone_tools.html.twig" as phone_tools %}

Of course, I could have given it a different alias than phone_tools, but I like to keep things coherent.

Usage :

{{ phone_tools.style_phone(phone) }}

Part 4 : Macros using Macros

Next, let’s suppose that our phones belong to a list of contacts :

Phone (contact #, prefix, number, type)

Contact (phones #, name)

  • The Phone class has a new member of type Contact (@ManyToOne(targetEntity="Contact", inversedBy="phones"))
  • The Contact class has an array of Phones (@OneToMany(targetEntity="Phone", mappedBy="contact"))

In order to create a phone-book for a list of contacts, we will be using a second macro create_phonebook , which makes use of the style_phone macro we created before.

Both macros can be placed in the same template, or in different templates. In all cases, the first macro needs to be imported before it can be used in the second. If both macros share the same template, the first one can be imported into the second using the twig variable _self .

{% macro create_phonebook(contacts) %}

    {% import _self as phone_tools %}

    {% if contacts is not null %}
        {% for contact in contacts %}
            {% if contact.phones is not null %}
                {% for phone in contact.phones %}
                
                    {{ phone_tools.style_phone(phone) }}
                    
                {% endfor %}
            {% endif %}
        {% endfor %}
    {% endif %}

{% endmacro %}
  • First of all, we import the first macro (line 3)
  • Next, we circle through the contacts and for each contacts through its phone numbers (lines 5-8)
  • And for each phone we use the style_phone macro to generate its template block

As with the first example, we need to import our macro template file before using the macro :

{% import "@CookiejarBundle/Phone/phone_tools.html.twig" as phone_tools %}

And then we can use both of our macros :

{{ phone_tools.style_phone(phone) }}

{{ phone_tools.create_phonebook(phone) }}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.