タイトル: テンプレートエンジン
SEOタイトル: PHP のテンプレートエンジン比較(Twig / Blade / Smarty / Plates / Mustache)
| この記事の要点 |
|
なぜテンプレートエンジンを使うのか
素の PHP でも HTML を出力できますが、以下の問題があります:
- HTML エスケープが手動 → 漏れると XSS
- ロジックと表示が混在しやすく可読性が悪い
- 共通レイアウトの継承が冗長(毎ファイルにヘッダー・フッターの include)
- 非エンジニア(デザイナー)に PHP を触らせたくない
テンプレートエンジンはこれらを制約付き構文 + 自動エスケープで解決します。
5 大テンプレートエンジンの比較
| エンジン | 用途 | 特徴 |
|---|---|---|
| Twig | Symfony / フレームワーク非依存 | モダン・高速・継承・サンドボックス。Drupal でも採用 |
| Blade | Laravel 専用 | @directive 構文・コンポーネント・スロット |
| Smarty | 老舗 (2001-) | 歴史長くドキュメント豊富。記法はやや古い |
| Plates | 素の PHP に近い | 独自構文学習不要・軽量・継承サポート |
| Mustache | ロジックレス・多言語 | JS/Ruby/Go 等にも実装あり。{{var}} のみ |
Twig の基本
{# layout.twig - 共通レイアウト #}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}デフォルト{% endblock %}</title>
</head>
<body>
<header>共通ヘッダー</header>
{% block content %}{% endblock %}
<footer>© {{ "now"|date("Y") }}</footer>
</body>
</html>
{# page.twig - 継承 #}
{% extends "layout.twig" %}
{% block title %}記事一覧{% endblock %}
{% block content %}
<h1>{{ pageTitle }}</h1>
{% if articles is not empty %}
<ul>
{% for a in articles %}
<li><a href="/article/{{ a.id }}">{{ a.title }}</a> ({{ a.views|number_format }} views)</li>
{% else %}
<li>記事がありません</li>
{% endfor %}
</ul>
{% endif %}
{# 自動エスケープ #}
{{ user.name }} {# <script> はエスケープされる #}
{{ user.bio|raw }} {# raw = エスケープしない(信頼できる時のみ) #}
{{ price|number_format(0, ',', '.') }}
{% endblock %}
PHP からの呼び出し:
<?php
require __DIR__ . '/vendor/autoload.php';
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/templates');
$twig = new \Twig\Environment($loader, [
'cache' => __DIR__ . '/cache',
'auto_reload' => true, // 開発時は変更検知
'autoescape' => 'html',
]);
echo $twig->render('page.twig', [
'pageTitle' => '記事一覧',
'articles' => [
['id' => 1, 'title' => 'PHP 入門', 'views' => 12345],
['id' => 2, 'title' => 'Laravel', 'views' => 9876],
],
'user' => ['name' => 'taro', 'bio' => '<b>Hello</b>'],
]);
Blade の基本(Laravel)
{{-- layouts/app.blade.php --}}
<!DOCTYPE html>
<html>
<head>
<title>@yield('title', 'デフォルト')</title>
</head>
<body>
@include('partials.header')
@yield('content')
@stack('scripts')
</body>
</html>
{{-- pages/articles.blade.php --}}
@extends('layouts.app')
@section('title', '記事一覧')
@section('content')
<h1>{{ $pageTitle }}</h1>
@forelse ($articles as $a)
<div>
<a href="/article/{{ $a->id }}">{{ $a->title }}</a>
({{ number_format($a->views) }} views)
</div>
@empty
<p>記事がありません</p>
@endforelse
{{ $user->name }} {{-- 自動エスケープ --}}
{!! $user->bio !!} {{-- エスケープ無効(信頼できる時のみ) --}}
@endsection
@push('scripts')
<script src="/js/articles.js"></script>
@endpush
コンポーネント(Blade の現代的な機能):
{{-- resources/views/components/alert.blade.php --}}
@props(['type' => 'info'])
<div class="alert alert-{{ $type }}">
{{ $slot }}
</div>
{{-- 使用 --}}
<x-alert type="success">
保存しました!
</x-alert>
Smarty / Plates / Mustache の例
{* Smarty 4 *}
{extends file="layout.tpl"}
{block name="content"}
<h1>{$pageTitle|escape}</h1>
{foreach $articles as $a}
<li>{$a.title|escape}</li>
{/foreach}
{/block}<?php
// Plates — 素の PHP に近い
// templates/page.php
$this->layout('layout', ['title' => $pageTitle]);
?>
<h1><?= $this->e($pageTitle) ?></h1>
<?php foreach ($articles as $a): ?>
<li><?= $this->e($a['title']) ?></li>
<?php endforeach; ?>{{! Mustache - ロジックレス }}
<h1>{{pageTitle}}</h1>
<ul>
{{#articles}}
<li>{{title}} ({{views}} views)</li>
{{/articles}}
{{^articles}}
<li>記事がありません</li>
{{/articles}}
</ul>
選び方の指針
| 状況 | 推奨 |
|---|---|
| Laravel を使う | Blade(標準・統合済) |
| Symfony を使う | Twig(標準・統合済) |
| フレームワーク非依存・新規 | Twig(最も成熟) |
| 軽量・素の PHP に近い構文がいい | Plates |
| JS / Ruby と同じテンプレートを共有 | Mustache |
| 既存資産が大量にある | そのまま継続(Smarty 等) |
パフォーマンス: コンパイルキャッシュ
主要エンジンは初回ロード時にテンプレートを純粋な PHP コードへコンパイルし、cache/ ディレクトリに保存します。2 回目以降はキャッシュされた PHP を require するだけなので、素の PHP と遜色ない速度になります。
デプロイ時のキャッシュ操作:
# Laravel Blade
php artisan view:clear
php artisan view:cache
# Twig はコンパイル済キャッシュをクリア(手動)
rm -rf var/cache/prod/twig
# Smarty
$smarty->clearCompiledTemplate();
FAQ
Q: 素の PHP では駄目?
A: 小さなページなら可。ただし htmlspecialchars() を毎回書くのは現実的に漏れる → XSS の温床。テンプレートエンジンは自動エスケープが最大の利点。
Q: Twig と Blade はどちらが速い?
A: 大きな差はなし。両方とも初回コンパイル後はキャッシュされた PHP として動く。OPcache 有効化のほうが影響大。
Q: テンプレートに DB クエリを書いていい?
A: 非推奨。表示ロジックとデータ取得は分離(MVC)。テンプレート内では 渡された変数を表示するだけに留めるとメンテしやすい。