Twig render vs include - When and where to use one or the other?

17,556

There are major differences between {% render %} and {% include %}.

  • {% render %} tag calls an action : when you do that, you are executing a controller, creating a new context inside that controller and renders a view that will be added to your current view.

  • {% include %} tag includes another twig file in the current one : there are no action called, so the included file will use your current context (or the context you give as parameter) to render the view.

Let's see that in details.


A {% render %} example

Render is a tag that calls an action the very same way as if you were calling it using a route, but internally, without HTTP transactions. Personally, I am using {% render %} when the content included to my view need to be refreshed using ajax. In that way, I'm able to call the same action using the standard routing when there is interactions inside my page.

Consider a simple page with an ajax form that helps you to add stuffs, and a dynamically refreshed table of stuffs.

enter image description here

The Stuff entity

<?php

// src/Fuz/HomeBundle/Entity/StuffData.php

namespace Fuz\HomeBundle\Entity;

class StuffData
{

    private $stuff;

    public function getStuff()
    {
        return $this->stuff;
    }

    public function setStuff($stuff)
    {
        $this->stuff = $stuff;
        return $this;
    }

}

The Stuff form

<?php

// src/Fuz/HomeBundle/Form/StuffType.php

namespace Fuz\HomeBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class StuffType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('stuff', 'text', array('label' => ''));
    }

    public function getDefaultOptions(array $options)
    {
        return array (
                'data_class' => 'Fuz\HomeBundle\Entity\StuffData',
        );
    }

    public function getName()
    {
        return "Stuff";
    }

}

The routing.yml file

# src/Fuz/HomeBundle/Resources/config/routing.yml

fuz_home:
    pattern:  /
    defaults: { _controller: FuzHomeBundle:Default:index }

fuz_add_stuff:
    pattern:  /add_stuff
    defaults: { _controller: FuzHomeBundle:Default:addStuff }

fuz_del_stuff:
    pattern:  /del_stuff
    defaults: { _controller: FuzHomeBundle:Default:delStuff }

fuz_list_stuffs:
    pattern:  /list_stuffs
    defaults: { _controller: FuzHomeBundle:Default:listStuffs }

The controllers

<?php

namespace Fuz\HomeBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Fuz\HomeBundle\Entity\StuffData;
use Fuz\HomeBundle\Form\StuffType;

class DefaultController extends Controller
{

    /**
     * Route : fuz_home
     */
    public function indexAction()
    {
        // Initialize some stuffs, stored in the session instead of in a table for simplicity
        if (!$this->get('session')->has('stuffs'))
        {
            $this->get('session')->set('stuffs', array());
        }

        // Create the form used to add a stuff
        $form = $this->createForm(new StuffType(), new StuffData());

        $twigVars = array(
                'formAddStuff' => $form->createView(),
        );

        return $this->render('FuzHomeBundle:Default:index.html.twig', $twigVars);
    }

    /**
     * Route : fuz_add_stuff
     */
    public function addStuffAction()
    {
        $data = new StuffData();
        $form = $this->createForm(new StuffType(), $data);
        $form->bindRequest($this->getRequest());
        if ($form->isValid())
        {
            $stuffs = $this->get('session')->get('stuffs');
            $stuffs[] = $data->getStuff();
            $this->get('session')->set('stuffs', $stuffs);
        }
        return $this->forward("FuzHomeBundle:Default:listStuffs");
    }

    /**
     * Route : fuz_del_stuff
     */
    public function delStuffAction()
    {
        $stuffId = $this->getRequest()->get('stuffId');
        $stuffs = $this->get('session')->get('stuffs');
        if (array_key_exists($stuffId, $stuffs))
        {
            unset($stuffs[$stuffId]);
            $this->get('session')->set('stuffs', array_values($stuffs));
        }
        return $this->forward("FuzHomeBundle:Default:listStuffs");
    }

    /**
     * Route : fuz_list_stuffs
     */
    public function listStuffsAction()
    {
        $stuffs = $this->get('session')->get('stuffs');
        $twigVars = array(
                'stuffs' => $stuffs,
        );
        return $this->render('FuzHomeBundle:Default:listStuffs.html.twig', $twigVars);
    }

The index.html.twig

{# src/Fuz/HomeBundle/Resources/views/Default/index.html.twig #}

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>

{# The form that will be posted asynchronously #}
<form id="formStuff">
    {{ form_widget(formAddStuff) }}
    <input type="button" id="add-stuff" value="Add stuff" />
</form>

<br/><br/>

{# The div that will contain the dynamic table #}
<div id="list-stuffs">
    {% render path('fuz_list_stuffs') %}
</div>

{# When a click is made on the add-stuff button, we post the form #}
<script type="text/javascript">
    $('#add-stuff').click(function() {
        $.post('{{ path('fuz_add_stuff') }}',  $('#formStuff').serialize(), function(data) {
            $('#list-stuffs').html(data);
         });
     });
</script>

The listStuffs.html.twig

{# listStuf

fs.html.twig #}

{% if stuffs | length == 0 %}

    No stuff to display !

{% else %}

    <table style="width: 50%">

        {% for stuffId, stuff in stuffs %}
            <tr>
                <td>{{ stuff }}</td>
                <td><a data-stuff-id="{{ stuffId }}" class="delete-stuff">Delete</a></td>
            </tr>
        {% endfor %}

    </table>

<script type="text/javascript">
    $('.delete-stuff').click(function() {
        $.post('{{ path('fuz_del_stuff') }}', {'stuffId': $(this).data('stuff-id')}, function(data) {
            $('#list-stuffs').html(data);
         });
     });
</script>

{% endif %}

This will give you some ugly form looking like this :

enter image description here

The point is : if you refresh your page or if you add/delete stuffs, the same controller will be called. No need to create some complex logic or to duplicate code.


An {% include %} example

The [% include %} tag let you include some piece of twig code about the same way as the include instruction works in PHP. This mean basically : {% include %} gives you a way to reuse some generic piece of code everywhere in your application.

enter image description here

We stay with our stuffs example : keep StuffEntity and StuffData but replace the following :

Routing :

fuz_home:
    pattern:  /
    defaults: { _controller: FuzHomeBundle:Default:index }

fuz_add_stuff:
    pattern:  /add_stuff
    defaults: { _controller: FuzHomeBundle:Default:addStuff }

fuz_del_stuff:
    pattern:  /del_stuff
    defaults: { _controller: FuzHomeBundle:Default:delStuff }

Controllers :

public function indexAction()
{
    // Initialize some stuffs, stored in the session instead of in a table for simplicity
    if (!$this->get('session')->has('stuffs'))
    {
        $this->get('session')->set('stuffs', array());
    }

    // Create the form used to add a stuff
    $form = $this->createForm(new StuffType(), new StuffData());
    $stuffs = $this->get('session')->get('stuffs');

    $twigVars = array(
            'formAddStuff' => $form->createView(),
            'stuffs' => $stuffs,
    );

    return $this->render('FuzHomeBundle:Default:index.html.twig', $twigVars);
}

/**
 * Route : fuz_add_stuff
 */
public function addStuffAction()
{
    $data = new StuffData();
    $form = $this->createForm(new StuffType(), $data);
    $form->bindRequest($this->getRequest());
    if ($form->isValid())
    {
        $stuffs = $this->get('session')->get('stuffs');
        $stuffs[] = $data->getStuff();
        $this->get('session')->set('stuffs', $stuffs);
    }
    return $this->forward("FuzHomeBundle:Default:index");
}

/**
 * Route : fuz_del_stuff
 */
public function delStuffAction()
{
    $stuffId = $this->getRequest()->get('id');
    $stuffs = $this->get('session')->get('stuffs');
    if (array_key_exists($stuffId, $stuffs))
    {
        unset($stuffs[$stuffId]);
        $this->get('session')->set('stuffs', array_values($stuffs));
    }
    return $this->forward("FuzHomeBundle:Default:index");
}

index.html.twig :

{# src/Fuz/HomeBundle/Resources/views/Default/index.html.twig #}

<form action="{{ path('fuz_add_stuff') }}" method="post">
    {{ form_widget(formAddStuff) }}
    <input type="submit" value="Add stuff" />
</form>

<br/><br/>

{# Here we include our "generic" table with the stuff table as parameter #}
{%
    include 'FuzHomeBundle:Default:genericTable.html.twig'
    with {
        'route': 'fuz_del_stuff',
        'data' : stuffs,
    }
%}

genericTable :

{# src/Fuz/HomeBundle/Resources/views/Default/genericTable.html.twig #}

{% if data | length == 0 %}

    No data to display !

{% else %}

    <table style="width: 50%">

        {% for id, elem in data %}
            <tr>
                <td>{{ elem }}</td>
                <td><a href="{{ path(route, {'id': id}) }}">Delete</a></td>
            </tr>
        {% endfor %}

    </table>

{% endif %}

As you can see here, there is only one controller that initialize the whole elements of the page (the form and the table), so that's not possible to do asynchronous transactions. But, you can include this genericTable.html.twig file anywhere in your application.


Conclusion

You will use {% render %} when the view to insert may be refreshed using a standard route or when the view to insert is totally independant from the current context.

You will use {% include %} when you need to use a piece of twig code several times in your application, but you will need to initialize the included view's required context in the same action as the parent twig file.

Share:
17,556
0x1gene
Author by

0x1gene

profile for 0x1gene on Stack Exchange, a network of free, community-driven Q&amp;A sites http://stackexchange.com/users/flair/2365547.png

Updated on June 03, 2022

Comments

  • 0x1gene
    0x1gene almost 2 years

    I've read Twig: render vs include but it isn't what I'm looking for. I am not sure where and when should I use render, and when should I use include, as the behavior of these expressions seems very similar to me.

    What is the fundamental differences between these two expressions ?

  • 0x1gene
    0x1gene about 11 years
    Thanks I didn't thought about Ajax, your answear fit well my needs !
  • SirDerpington
    SirDerpington about 11 years
    I didn't know about the {% render %} tag in twig. Can you point me to a docu entry or some samples? It seems really interesting
  • Alain Tiemblo
    Alain Tiemblo about 11 years
    Hmm, it looks not documented a lot, have a look to the old Symfony2 doc, and search for Embedding Controllers.
  • SirDerpington
    SirDerpington about 11 years
    Thanks! There's really not much documented about it. But I think this might come in hand sometime. +1 :D
  • hounded
    hounded almost 8 years
    Wish I could upvote this more than once such a great example!