Twigとは
TwigはSymfonyで標準採用されているPHP用のテンプレートエンジンです。HTMLとロジックを分離し、読みやすく保守しやすいテンプレートを記述できます。PHPそのものをテンプレートに書くのではなく、Twig独自のシンプルな構文を使います。
主な特徴は以下のとおりです。
- シンプルな構文:
{{ }}で変数を出力、{% %}で制御構文を書く - テンプレート継承:共通レイアウトを親テンプレートとして定義し、子テンプレートで拡張できる
- 自動エスケープ:XSS対策としてデフォルトでHTMLエスケープが有効
- 豊富なフィルタと関数:文字列・配列・日付などの操作が組み込み済み
基本的な構文
変数の出力
{{ 変数名 }} で変数をHTMLに出力します。
{# templates/greeting.html.twig #}
<h1>こんにちは、{{ name }} さん!</h1>
<p>今日は {{ today }} です。</p>
Symfonyのコントローラからテンプレートに値を渡すには、render() メソッドで配列を渡します。
// src/Controller/GreetingController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class GreetingController extends AbstractController
{
#[Route('/greeting', name: 'app_greeting')]
public function index(): Response
{
return $this->render('greeting.html.twig', [
'name' => '太郎',
'today' => (new \DateTime())->format('Y年m月d日'),
]);
}
}
制御構文(if・for)
{% if %} で条件分岐、{% for %} でループ処理を記述します。
{# 条件分岐 #}
{% if user.isAdmin %}
<p>管理者としてログイン中です。</p>
{% elseif user.isLoggedIn %}
<p>ようこそ、{{ user.name }} さん。</p>
{% else %}
<p>ログインしてください。</p>
{% endif %}
{# ループ #}
<ul>
{% for item in items %}
<li>{{ loop.index }}. {{ item.name }}({{ item.price }}円)</li>
{% else %}
<li>商品がありません。</li>
{% endfor %}
</ul>
loop.index はループの現在のインデックス(1始まり)を返す特別な変数です。loop.first や loop.last で最初・最後の判定もできます。
テンプレートの継承
Twigの最も強力な機能のひとつがテンプレート継承です。サイト共通のレイアウトを親テンプレートに定義し、ページごとに変わる部分だけを子テンプレートで上書きできます。
親テンプレート(base.html.twig)
{# templates/base.html.twig #}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{% block title %}マイサイト{% endblock %}</title>
{% block stylesheets %}{% endblock %}
</head>
<body>
<header>
<nav>共通ナビゲーション</nav>
</header>
<main>
{% block body %}{% endblock %}
</main>
<footer>
<p>© 2026 マイサイト</p>
</footer>
{% block javascripts %}{% endblock %}
</body>
</html>
{% block ブロック名 %}{% endblock %} で子テンプレートが上書きできる領域を定義します。
子テンプレート
{# templates/product/list.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}商品一覧 - マイサイト{% endblock %}
{% block body %}
<h1>商品一覧</h1>
<ul>
{% for product in products %}
<li>
<a href="{{ path('app_product_show', { id: product.id }) }}">
{{ product.name }}
</a>
- {{ product.price|number_format(0, '.', ',') }}円
</li>
{% endfor %}
</ul>
{% endblock %}
{% extends %} で親テンプレートを指定し、各 {% block %} セクションを子テンプレートで上書きします。
フィルタと関数
よく使うフィルタ
フィルタは {{ 値|フィルタ名 }} の形式で使います。
{# 文字列の操作 #}
{{ 'hello world'|upper }} {# HELLO WORLD #}
{{ 'HELLO WORLD'|lower }} {# hello world #}
{{ ' spaces '|trim }} {# spaces #}
{{ 'long text here'|truncate(8) }} {# long tex... #}
{# 数値のフォーマット #}
{{ 1234567|number_format(0, '.', ',') }} {# 1,234,567 #}
{# 日付のフォーマット #}
{{ post.createdAt|date('Y年m月d日') }}
{# デフォルト値 #}
{{ user.nickname|default('名無し') }}
{# 配列の操作 #}
{{ items|length }} {# 要素数 #}
{{ items|join(', ') }} {# カンマ区切りで結合 #}
{{ items|sort }} {# ソート #}
raw フィルタと自動エスケープ
TwigはデフォルトでHTML特殊文字をエスケープします。HTMLタグを含む文字列をそのまま出力したい場合は |raw フィルタを使いますが、ユーザー入力に対して使うのは危険です。信頼できるデータにのみ使用してください。
{# エスケープあり(デフォルト) #}
{{ '<b>太字</b>' }} {# <b>太字</b> として出力 #}
{# エスケープなし(信頼できるデータのみ) #}
{{ article.htmlContent|raw }}
マクロで再利用可能なコンポーネントを作る
Twigのマクロは、再利用可能なテンプレート部品を定義する機能です。
{# templates/macros/form.html.twig #}
{% macro input(name, value, type = 'text', label = '') %}
<div class="form-group">
{% if label %}
<label for="{{ name }}">{{ label }}</label>
{% endif %}
<input type="{{ type }}" id="{{ name }}" name="{{ name }}" value="{{ value }}">
</div>
{% endmacro %}
{# 別テンプレートから使う #}
{% import 'macros/form.html.twig' as forms %}
<form method="post">
{{ forms.input('username', '', 'text', 'ユーザー名') }}
{{ forms.input('password', '', 'password', 'パスワード') }}
<button type="submit">ログイン</button>
</form>
まとめ
Twigの主要な機能をまとめると以下のとおりです。
| 機能 | 構文 | 用途 |
|---|---|---|
| 変数出力 | {{ 変数 }} | 値の表示 |
| 制御構文 | {% if %} {% for %} | 条件分岐・ループ |
| テンプレート継承 | {% extends %} {% block %} | レイアウト共通化 |
| フィルタ | 値|フィルタ名 | 値の変換・整形 |
| マクロ | {% macro %} | 再利用可能な部品 |
| コメント | {# コメント #} | テンプレート内注釈 |
Twigを使いこなすことで、PHPのロジックとHTMLの見た目を明確に分離でき、チーム開発でのメンテナンス性が大幅に向上します。Symfonyプロジェクトではまずテンプレート継承とフィルタをしっかりと押さえておきましょう。