この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:4
ページ更新者:atom
更新日時:2026-06-11 07:07:02

タイトル: テンプレートエンジン
SEOタイトル: PHP のテンプレートエンジン比較(Twig / Blade / Smarty / Plates / Mustache)

この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li>テンプレートエンジン = <strong>HTML の中に変数や制御構文を埋め込み、HTML エスケープを自動化</strong>するライブラリ</li><li>主要 5 つ: <strong>Twig</strong> (Symfony 標準・モダン) / <strong>Blade</strong> (Laravel 専用) / <strong>Smarty</strong> (老舗) / <strong>Plates</strong> (素の PHP に近い) / <strong>Mustache</strong> (ロジックレス・多言語)</li><li>共通機能: <strong>変数展開・制御構文・テンプレート継承 (extends)・include / partial・フィルタ・自動エスケープ</strong></li><li>パフォーマンス: 初回コンパイルして PHP に変換 → 以降は <strong>コンパイル済キャッシュ</strong>使用</li><li>素の PHP を使う場合は <code><?= htmlspecialchars($x, ENT_QUOTES, "UTF-8") ?></code> を毎回書く必要 → 漏れると <strong>XSS の温床</strong></li></ul></td></tr></table>
<h2>なぜテンプレートエンジンを使うのか</h2>

<p>素の PHP でも HTML を出力できますが、以下の問題があります:</p>

<ul>
<li><strong>HTML エスケープが手動</strong> → 漏れると XSS</li>
<li><strong>ロジックと表示が混在</strong>しやすく可読性が悪い</li>
<li><strong>共通レイアウトの継承</strong>が冗長(毎ファイルにヘッダー・フッターの include)</li>
<li><strong>非エンジニア(デザイナー)</strong>に PHP を触らせたくない</li>
</ul>

<p>テンプレートエンジンはこれらを<strong>制約付き構文 + 自動エスケープ</strong>で解決します。</p>

<h2>5 大テンプレートエンジンの比較</h2>

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>エンジン</th><th>用途</th><th>特徴</th></tr></thead>
<tbody>
<tr><td>Twig</td><td>Symfony / フレームワーク非依存</td><td>モダン・高速・継承・サンドボックス。Drupal でも採用</td></tr>
<tr><td>Blade</td><td>Laravel 専用</td><td><code>@directive</code> 構文・コンポーネント・スロット</td></tr>
<tr><td>Smarty</td><td>老舗 (2001-)</td><td>歴史長くドキュメント豊富。記法はやや古い</td></tr>
<tr><td>Plates</td><td>素の PHP に近い</td><td>独自構文学習不要・軽量・継承サポート</td></tr>
<tr><td>Mustache</td><td>ロジックレス・多言語</td><td>JS/Ruby/Go 等にも実装あり。<code>{{var}}</code> のみ</td></tr>
</tbody>
</table>

<h2>Twig の基本</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-twig">{# layout.twig - 共通レイアウト #}
<!DOCTYPE html>
<html>
<head>
  <title>{% block title %}デフォルト{% endblock %}</title>
</head>
<body>
  <header>共通ヘッダー</header>
  {% block content %}{% endblock %}
  <footer>&copy; {{ "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, &apos;,&apos;, &apos;.&apos;) }}
{% endblock %}</code></pre>
<p>PHP からの呼び出し:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php"><?php
require __DIR__ . &apos;/vendor/autoload.php&apos;;

$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . &apos;/templates&apos;);
$twig = new \Twig\Environment($loader, [
    &apos;cache&apos; => __DIR__ . &apos;/cache&apos;,
    &apos;auto_reload&apos; => true,   // 開発時は変更検知
    &apos;autoescape&apos; => &apos;html&apos;,
]);

echo $twig->render(&apos;page.twig&apos;, [
    &apos;pageTitle&apos; => &apos;記事一覧&apos;,
    &apos;articles&apos; => [
        [&apos;id&apos; => 1, &apos;title&apos; => &apos;PHP 入門&apos;, &apos;views&apos; => 12345],
        [&apos;id&apos; => 2, &apos;title&apos; => &apos;Laravel&apos;, &apos;views&apos; => 9876],
    ],
    &apos;user&apos; => [&apos;name&apos; => &apos;taro&apos;, &apos;bio&apos; => &apos;<b>Hello</b>&apos;],
]);</code></pre>
<h2>Blade の基本(Laravel)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-blade">{{-- layouts/app.blade.php --}}
<!DOCTYPE html>
<html>
<head>
  <title>@yield(&apos;title&apos;, &apos;デフォルト&apos;)</title>
</head>
<body>
  @include(&apos;partials.header&apos;)
  @yield(&apos;content&apos;)
  @stack(&apos;scripts&apos;)
</body>
</html>

{{-- pages/articles.blade.php --}}
@extends(&apos;layouts.app&apos;)

@section(&apos;title&apos;, &apos;記事一覧&apos;)

@section(&apos;content&apos;)
  <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(&apos;scripts&apos;)
  <script src="/js/articles.js"></script>
@endpush</code></pre>
<p>コンポーネント(Blade の現代的な機能):</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-blade">{{-- resources/views/components/alert.blade.php --}}
@props([&apos;type&apos; => &apos;info&apos;])

<div class="alert alert-{{ $type }}">
  {{ $slot }}
</div>

{{-- 使用 --}}
<x-alert type="success">
  保存しました!
</x-alert></code></pre>
<h2>Smarty / Plates / Mustache の例</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-smarty">{* Smarty 4 *}
{extends file="layout.tpl"}
{block name="content"}
  <h1>{$pageTitle|escape}</h1>
  {foreach $articles as $a}
    <li>{$a.title|escape}</li>
  {/foreach}
{/block}</code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php"><?php
// Plates — 素の PHP に近い
// templates/page.php
$this->layout(&apos;layout&apos;, [&apos;title&apos; => $pageTitle]);
?>
<h1><?= $this->e($pageTitle) ?></h1>
<?php foreach ($articles as $a): ?>
  <li><?= $this->e($a[&apos;title&apos;]) ?></li>
<?php endforeach; ?></code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-mustache">{{! Mustache - ロジックレス }}
<h1>{{pageTitle}}</h1>
<ul>
  {{#articles}}
    <li>{{title}} ({{views}} views)</li>
  {{/articles}}
  {{^articles}}
    <li>記事がありません</li>
  {{/articles}}
</ul></code></pre>
<h2>選び方の指針</h2>

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>状況</th><th>推奨</th></tr></thead>
<tbody>
<tr><td>Laravel を使う</td><td>Blade(標準・統合済)</td></tr>
<tr><td>Symfony を使う</td><td>Twig(標準・統合済)</td></tr>
<tr><td>フレームワーク非依存・新規</td><td>Twig(最も成熟)</td></tr>
<tr><td>軽量・素の PHP に近い構文がいい</td><td>Plates</td></tr>
<tr><td>JS / Ruby と同じテンプレートを共有</td><td>Mustache</td></tr>
<tr><td>既存資産が大量にある</td><td>そのまま継続(Smarty 等)</td></tr>
</tbody>
</table>

<h2>パフォーマンス: コンパイルキャッシュ</h2>

<p>主要エンジンは初回ロード時にテンプレートを<strong>純粋な PHP コードへコンパイル</strong>し、<code>cache/</code> ディレクトリに保存します。2 回目以降はキャッシュされた PHP を <code>require</code> するだけなので、<strong>素の PHP と遜色ない速度</strong>になります。</p>

<p>デプロイ時のキャッシュ操作:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-bash"># Laravel Blade
php artisan view:clear
php artisan view:cache

# Twig はコンパイル済キャッシュをクリア(手動)
rm -rf var/cache/prod/twig

# Smarty
$smarty->clearCompiledTemplate();</code></pre>
<h2>FAQ</h2>

<p><strong>Q: 素の PHP では駄目?</strong><br />
A: 小さなページなら可。ただし <strong>htmlspecialchars()</strong> を毎回書くのは現実的に漏れる → XSS の温床。テンプレートエンジンは自動エスケープが最大の利点。</p>

<p><strong>Q: Twig と Blade はどちらが速い?</strong><br />
A: 大きな差はなし。両方とも初回コンパイル後はキャッシュされた PHP として動く。<strong>OPcache</strong> 有効化のほうが影響大。</p>

<p><strong>Q: テンプレートに DB クエリを書いていい?</strong><br />
A: 非推奨。表示ロジックとデータ取得は分離(MVC)。テンプレート内では <strong>渡された変数を表示するだけ</strong>に留めるとメンテしやすい。</p>