symfony/twig-bundle
TwigBundle integrates Twig templating into the Symfony full-stack framework, providing seamless configuration and services for rendering templates. Includes links for contributing, reporting issues, and submitting pull requests via the main Symfony repository.
Installation:
composer require symfony/twig-bundle
The bundle auto-registers when Symfony Flex detects it. No manual bundles.php entry is needed in Symfony 5+.
First Use Case:
Create a template in templates/base.html.twig:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Welcome!{% endblock %}</title>
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
Extend it in templates/index.html.twig:
{% extends 'base.html.twig' %}
{% block body %}
<h1>Hello, {{ name }}!</h1>
{% endblock %}
Render in Controller:
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomeController extends AbstractController
{
#[Route('/')]
public function index(): Response
{
return $this->render('index.html.twig', [
'name' => 'World',
]);
}
}
Verify:
Run php bin/console server:run and visit /. Twig auto-reloads during development.
Check config/packages/twig.yaml for defaults:
twig:
default_path: '%kernel.project_dir%/templates'
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
auto_reload: '%kernel.debug%'
cache: '%kernel.cache_dir%/twig'
var/cache/dev/twig/ (development) or var/cache/prod/twig/ (production)config/packages/twig.yaml under globals.Template Inheritance & Blocks:
{% extends %} and {% block %} for reusable layouts.{% block stylesheets %}{% endblock %} for child templates to override.Macros for Reusable Components:
{# templates/_components/alert.html.twig #}
{% macro alert(type, message) %}
<div class="alert alert-{{ type }}">
{{ message }}
</div>
{% endmacro %}
Include in templates:
{% import '_components/alert.html.twig' as alert %}
{{ alert.alert('success', 'Operation completed!') }}
Dynamic Content with Embeds:
{% embed '_partials/header.html.twig' %}
{% block title %}Custom Title{% endblock %}
{% endembed %}
Form Integration:
{{ form_start(form) }}
{{ form_row(form.name) }}
{{ form_row(form.email) }}
{{ form_end(form) }}
Auto-generates HTML with CSRF protection and validation errors.
Asset Management:
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
Uses Symfony’s asset() function for versioned URLs.
Custom Twig Extensions:
Create a service with #[AsTwigExtension]:
use Symfony\Component\DependencyInjection\Attribute\AsTwigExtension;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
#[AsTwigExtension]
class MyTwigExtension extends AbstractExtension
{
public function getFunctions(): array
{
return [
new TwigFunction('greet', [$this, 'greetUser']),
];
}
public function greetUser(string $name): string
{
return "Hello, $name!";
}
}
Use in templates: {{ greet('Alice') }}.
Filters and Tests:
#[AsTwigFilter(name: 'uppercase_first')]
public function uppercaseFirst(string $string): string
{
return ucfirst(strtolower($string));
}
#[AsTwigTest]
public function isEven(int $number): bool
{
return $number % 2 === 0;
}
Usage: {{ 'hello'|uppercase_first }} or {% if 4 is even %}...{% endif %}.
Event-Driven Templating:
Listen to TwigTemplateEvent to modify templates dynamically:
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: KernelEvents::VIEW, method: 'onKernelView')]
public function onKernelView(ViewEvent $event): void
{
if (!$event->getController() instanceof Controller) {
return;
}
$template = $event->getController()[1];
if (str_contains($template, 'index.html.twig')) {
$event->getController()[1] = 'custom_template.html.twig';
}
}
Caching Strategies:
auto_reload: true (default).cache: true and set cache_dir to var/cache/prod/twig.kernel.build_dir for templates known at compile time (Symfony 7.3+):
twig:
preload: true
Security Context:
strict_variables: true in development to catch undefined variables.{{ app.user|json_encode|raw }} to pass data safely to JavaScript.Bundle-Specific Templates:
Place templates in src/MyBundle/Resources/views/ and reference them with @MyBundle/views/template.html.twig.
Environment-Specific Templates:
Use config/packages/dev/twig.yaml to override settings per environment.
Twig in CLI Commands:
use Symfony\Component\HttpKernel\KernelInterface;
use Twig\Environment;
public function __construct(
private KernelInterface $kernel,
private Environment $twig
) {}
public function execute(): void
{
$html = $this->twig->render('emails/welcome.html.twig', [
'name' => 'User',
]);
// Send email...
}
Register Twig\Environment as a service in config/services.yaml.
Testing Templates:
Use Twig\Test\IntegrationTestCase or mock the Twig\Environment:
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class MyTest extends WebTestCase
{
public function testTemplateRendering()
{
$client = static::createClient();
$client->request('GET', '/');
$this->assertSelectorTextContains('h1', 'Hello, World!');
}
}
Template Not Found Errors:
default_path in twig.yaml.templates/ or use @Bundle/views/.php bin/console debug:container twig to check paths.Caching Issues:
php bin/console cache:clear) or use auto_reload: true in dev.twig.preload: true to compile templates at build time.Undefined Variables:
strict_variables: false (default in prod) hides errors.strict_variables: true in dev or use {{ dump(var) }} to debug.Asset Versioning:
/css/app.css) break caching.{{ asset('css/app.css') }} for versioned URLs.Macro Scope:
{% import '_macros.html.twig' as macros %} to share macros across templates.CSRF Token Mismatch:
form_start() without a form instance or in non-GET requests.{{ form_start(form) }}.Twig vs. PHP Logic:
How can I help you explore Laravel packages today?