I would like to render a tree with an undetermined depth (children of children of children, etc.). I need to loop through the array recursively; how can I do this in Twig?
6 Answers
I played around with domi27's idea and came up with this. I made a nested array as my tree, ['link']['sublinks'] is null or another array of more of the same.
Templates
The sub-template file to recurse with:
<!--includes/menu-links.html-->
{% for link in links %}
<li>
<a href="{{ link.href }}">{{ link.name }}</a>
{% if link.sublinks %}
<ul>
{% include "includes/menu-links.html" with {'links': link.sublinks} %}
</ul>
{% endif %}
</li>
{% endfor %}
Then in the main template, call this (kind of redundant 'with' stuff there):
<ul class="main-menu">
{% include "includes/menu-links.html" with {'links':links} only %}
</ul>
Macros
A similar effect can be achieved with macros:
<!--macros/menu-macros.html-->
{% macro menu_links(links) %}
{% for link in links %}
<li>
<a href="{{ link.href }}">{{ link.name }}</a>
{% if link.sublinks %}
<ul>
{{ _self.menu_links(link.sublinks) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
In the main template, do this:
{% import "macros/menu-macros.html" as macros %}
<ul class="main-menu">
{{ macros.menu_links(links) }}
</ul>

- 30,738
- 21
- 105
- 131

- 1,269
- 1
- 7
- 2
-
9Very good, thank you! If you want to use the macro in the same template you can use `{{ _self.menu_links(links) }}`. – flu Aug 10 '12 at 10:46
-
thank you, the thought of this made my brain hurt but your answer makes perfect sense. – azzy81 Aug 21 '13 at 13:56
-
I had one issue with my project with comments. subcomments(sublinks) were also included in main collection(links). so before include I had to check if comment had a 'parent' entry. – Jevgeni Smirnov Sep 28 '13 at 21:30
-
4Using `{{_self.menu_links}}` is a **bad practice**! Read a note here: [macro](http://twig.sensiolabs.org/doc/tags/macro.html) When you define a macro in the template where you are going to use it, you might be tempted to call the macro directly via _self.input() instead of importing it; even if seems to work, this is just a side-effect of the current implementation and it won't work anymore in Twig 2.x. You should import macroses locally once again insite `menu_links` – dr.scre Apr 04 '14 at 17:23
Twig 2.0 - 2.11
If you want to use a macro in the same template, you should use something like this to stay compatible with Twig 2.x:
{% macro menu_links(links) %}
{% import _self as macros %}
{% for link in links %}
<li>
<a href="{{ link.href }}">{{ link.name }}</a>
{% if link.sublinks %}
<ul>
{{ macros.menu_links(link.sublinks) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
{% import _self as macros %}
<ul class="main-menu">
{{ macros.menu_links(links) }}
</ul>
This extends random-coder
's answer and incorporates dr.scre
's hint to the Twig documentation about macros to now use _self
, but import locally.
Twig >= 2.11
As of Twig 2.11, you can omit the {% import _self as macros %}
, as inlined macros are imported automatically under the _self
namespace (see Twig announcement: Automatic macro import):
{# {% import _self as macros %} - Can be removed #}
<ul class="main-menu">
{{ _self.menu_links(links) }} {# Use _self for inlined macros #}
</ul>

- 14,307
- 8
- 74
- 71
If you're running PHP 5.4 or higher, there is a wonderful new solution (as of May 2016) to this problem by Alain Tiemblo: https://github.com/ninsuo/jordan-tree.
It's a "tree" tag that serves this exact purpose. Markup would look like this:
{% tree link in links %}
{% if treeloop.first %}<ul>{% endif %}
<li>
<a href="{{ link.href }}">{{ link.name }}</a>
{% subtree link.sublinks %}
</li>
{% if treeloop.last %}</ul>{% endif %}
{% endtree %}

- 1,573
- 1
- 17
- 24

- 2,773
- 2
- 26
- 39
-
1You can't pass additional variables to `subtree`. In my case, the code needs to know whether there will be more children and it passes the number of levels to the macro so it can do a ``. Calculating this would require iterating the current level a second time just to capture whether there are any children.– chx Apr 14 '19 at 17:33
Took flu's answer and modified it a little:
{# Macro #}
{% macro tree(items) %}
{% import _self as m %}
{% if items %}
<ul>
{% for i in items %}
<li>
<a href="{{ i.url }}">{{ i.title }}</a>
{{ m.tree(i.items) }}
</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %}
{# Usage #}
{% import 'macros.twig' as m %}
{{ m.tree(items) }}

- 30,738
- 21
- 105
- 131

- 73
- 2
- 8
First I thought this may be solved in a straightforward way, but it isn't that easy.
You need to create logic, maybe with a PHP class method, when to include a Twig subtemplate and when not.
<!-- tpl.html.twig -->
<ul>
{% for key, item in menu %}
{# Pseudo Twig code #}
{% if item|hassubitem %}
{% include "subitem.html.tpl" %}
{% else %}
<li>{{ item }}</li>
{% endif %}
{% endfor %}
</ul>
So you could use the special Twig loop variable, which is available inside a Twig for loop. But I'm not sure about the scope of this loop variable.
This and other information are available on Twigs "for" Docu!

- 30,738
- 21
- 105
- 131

- 6,903
- 2
- 21
- 33
The answers here lead my to my solution.
I have a category entity with a self-referencing many-to-one association (parent to children).
/**
* @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
*/
private $parent;
/**
* @ORM\OneToMany(targetEntity="Category", mappedBy="parent")
*/
private $children;
In my Twig template I am rendering the tree view like this:
<ul>
{% for category in categories %}
{% if category.parent == null %}
<li>
<a href="{{ category.id }}">{{ category.name }}</a>
{% if category.children|length > 0 %}
<ul>
{% for category in category.children %}
<li>
<a href="{{ category.id }}">{{ category.name }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul>

- 30,738
- 21
- 105
- 131

- 491
- 1
- 5
- 11