Насколько "быстро" можно вставить в twig-шаблоны виджет

Введение

В данной статье речь пойдет о том, как организовать код так, чтобы иметь возможность вставлять в разных twig-шаблонах одинаковые блоки (виджеты). Рассмотрим несколько вариантов реализации такого механизма.

WidgetController

Данный способ предполагает, что виджеты являются методами контроллера. Пример

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class WidgetController extends AbstractController
{
	public function widget(): Response
	{
		return $this->render('widget.html.twig', ['number' => rand()]);
	}
}

В шаблоне блок можно отрисовать так:

{{ render(controller('App\\Controller\\WidgetController:widget')) }} 

Данный способ описывается в документации symfony и содержит следующий недостаток

Rendering embedded controllers is “heavier” than including a template or calling a custom Twig function. Unless you’re planning on caching the fragment, avoid embedding many controllers.

Include

Самый просто механизм вставки блоков без доп. логики

{% include 'widget.html.twig' with {number:random()} %}
Если для виджета требуется получение данных, то данный способ не подойдет.

Twig extension

С помощью данного метода можно реализовать логику получения данных, аналогичную первому методу, но без оверхеда, связанного с созданием подзапроса. Пример:

<?php

namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class WidgetExtension extends AbstractExtension
{
	/** @var \Twig_Environment */
	private $twig;

    /**
     * @param \Twig_Environment $twig
     */
    public function __construct(\Twig_Environment $twig)
    {
        $this->twig = $twig;
    }
    
    /**
     * @return array
     */
    public function getFunctions(): array
    {
        return [
            new TwigFunction('lucky_widget', [$this, 'renderLuckyWidget'], ['is_safe' => ['html']]),
        ];
    }
    
    /**
     * @return string
     * @throws \Twig_Error_Loader
     * @throws \Twig_Error_Runtime
     * @throws \Twig_Error_Syntax
     */
    public function renderLuckyWidget(): string
    {
        return $this->twig->render('widget.html.twig', ['number' => rand()]);
    }

}

Сравнение

Чтобы сравнить, какой из перечисленных выше способов отработает быстрее, создадим небольшой проект и измерим с помощью простого бенчмарка ab скорость отрисовки. Ниже представлен график времени отрисовки для всех трех методов.

Мы видим, что лучше всего себя показал twig extension. Однако, если мы обратим внимание на абсолютное значение времени отрисовки, выигрыш не кажется таким существенным.