Twig render vs include - Когда и где использовать тот или иной?

Я читал Twig: render vs include, но это не то, что я ищу. Я не уверен, где и когда я должен использовать render, и когда следует использовать include, поскольку поведение этих выражений кажется мне очень похожим.

В чем заключаются фундаментальные различия между этими двумя выражениями?

Ответ 1

Существуют значительные различия между {% render %} и {% include %}.

  • Тег
  • {% render %} вызывает действие: когда вы это делаете, вы выполняете контроллер, создаете новый контекст внутри этого контроллера и визуализируете представление, которое будет добавлено к вашему текущему виду.

    Тег
  • {% include %} содержит другой файл twig в текущем: нет действия, вызываемого, поэтому включенный файл будет использовать ваш текущий контекст (или контекст, который вы укажете в качестве параметра), чтобы отобразить представление.

Посмотрим, что подробно.


Пример {% render%}

Render - это тег, который вызывает действие так же, как если бы вы вызывали его с использованием маршрута, но внутри, без HTTP-транзакций. Лично я использую {% render %}, когда содержимое, включенное в мое представление, должно быть обновлено с помощью ajax. Таким образом, я могу вызвать одно и то же действие, используя стандартную маршрутизацию, когда есть взаимодействия внутри моей страницы.

Рассмотрим простую страницу с формой ajax, которая поможет вам добавить материал и динамически обновленную таблицу материалов.

enter image description here

Объект Stuff

<?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;
    }

}

Форма материала

<?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";
    }

}

Файл routing.yml

# 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 }

Контроллеры

<?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);
    }

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>

СписокStuffs.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 %}

Это даст вам некоторую уродливую форму, выглядящую так:

enter image description here

Дело в том, что если вы обновляете свою страницу или добавляете/удаляете материал, вызывается тот же контроллер. Не нужно создавать сложную логику или дублировать код.


Пример {% include%}

Тег [% include %} позволяет добавить часть кода твика примерно так же, как инструкция include работает в PHP. В основном это означает: {% include %} дает вам возможность повторно использовать какой-либо общий код кода в вашем приложении.

enter image description here

Оставайтесь с нашим примером: сохраняйте StuffEntity и StuffData, но замените следующее:

Маршрутизация:

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 }

Контроллеры:

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 %}

Как вы можете видеть здесь, есть только один контроллер, который инициализирует все элементы страницы (форму и таблицу), чтобы не было возможности выполнять асинхронные транзакции. Но вы можете включить этот файл genericTable.html.twig в любом месте приложения.


Заключение

Вы будете использовать {% render %}, когда представление для вставки может быть обновлено с использованием стандартного маршрута или когда представление для вставки полностью не зависит от текущего контекста.

Вы будете использовать {% include %}, когда вам понадобится несколько раз использовать фрагмент кода в вашем приложении, но вам нужно будет инициализировать включенный контекст требуемого представления в том же действии, что и родительский файл ветки.