第2回:Twig テンプレートの基本


この章では Twig テンプレートエンジンについて学びます。すでに Twig テンプレートエンジンについて知っている方は飛ばしていただいて構いません。


この章でやること

  • Twig の3種類の構文(出力・制御・コメント)を理解する

  • 変数・条件分岐・ループをTwig Playgroundで実際に動かして体験する

  • a-blog cms 独自の要素(モジュール・グローバル変数・フィルター)との関係を把握する

なぜ Twig なのか

a-blog cms は Ver. 3.2 から、従来の標準テンプレートに加えて、Twig によるテンプレート記述をサポートしています。

標準テンプレートは a-blog cms 独自の記法で設計されており、できることは豊富です。一方で、ループや条件分岐といった制御構文が独自仕様になっているため、シンプルな出力は簡単にできても、少し複雑なカスタマイズをしようとすると途端に覚えることが増えるという側面があります。

Twig は PHP 製のテンプレートエンジンで、Craft CMS・Symfony・Drupal などでも広く採用されています。変数の出力・ループ・条件分岐の書き方が、PHP や JavaScript などの一般的なプログラミング言語に近いため、「Twig の書き方を覚える」というより「すでに知っている書き方がそのまま使える」という感覚で書けます。

たとえば条件分岐なら、標準テンプレートでは a-blog cms 固有の構文を覚える必要がありますが、Twig では次のように書けます。

{% if entry.tags %}
  {% for tag in entry.tags %}
    <span>#{{ tag.name }}</span>
  {% endfor %}
{% endif %}

「タグがあれば一覧を出す」という意図が、コードを読んだだけで伝わります。Twigに慣れたメンバーがいれば、a-blog cmsを知らなくてもテンプレートのロジックを読み解けます。これはチーム開発や長期保守において大きなメリットになります。

どちらの形式でも a-blog cms の機能はすべて利用できます。新規テーマをTwigで書く、あるいは既存テーマをTwig化してメンテナンス性を高める、どちらの用途にも使えます。

まず動かしてみよう — Twig Playground

a-blog cms の環境がなくても、Twig Playground を使えばブラウザ上でTwigの構文をすぐに試せます。

この章では各サンプルコードをそのまま貼り付けて実行できます。環境構築を待たずに、まず「書いて動かす」体験から始めましょう。

エディターにシンタックスハイライトを設定する

.twig ファイルを素のまま開くと、{{ }}{% %} が通常のテキストと同じ色で表示されてしまい、構造が非常に読みづらくなります。コードを書き始める前に、シンタックスハイライトの拡張機能を入れておきましょう。

VS Code を使っている場合は、拡張機能マーケットプレイスで 「Twig Language 2」を検索してインストールしてください。

インストール後、VS Code の再起動(または「ウィンドウの再読み込み」)が必要になることがあります。

シンタックスハイライトがあると、{{ }}{% %}{# #} の3種類がそれぞれ異なる色で表示され、HTMLとTwig構文の境界が一目でわかるようになります。コード量が増えるほどその効果を実感できます。

1. Twig の3種類の構文

Twig には HTML の中に埋め込む構文が3種類あります。これだけ覚えれば、基本的なテンプレートは読み書きできます。

構文

用途

{{ }}

変数や式を出力する

{{ title }}

{% %}

タグ。ループ・条件分岐などの制御を書く

{% for item in list %}

{# #}

コメント(HTML出力には含まれない)

{# ここはメモ #}

これら3種類のデリミタさえ区別できれば、Twig のテンプレートファイルは読めるようになります。


2. 変数の出力

{{ }} で変数を出力します。

{{ title }}
{{ entry.id }}

Twig Playground で試す:

{% set name = "a-blog cms" %}
<p>{{ name }} へようこそ!</p>

変数が持つプロパティには .(ドット)でアクセスします。

Twig Playground で試す:

{% set entry = {
  title: "新しい季節限定メニューが登場しました",
  date: "2025-08-07",
  category: {
    name: "新商品"
  }
} %}

<h1>{{ entry.title }}</h1>
<p>{{ entry.date }}</p>
<span>{{ entry.category.name }}</span>

entry.category.name のように、オブジェクトのネストも . をつなぐだけでアクセスできます。

グローバル変数も同じ記法

標準テンプレートでは %{ENTRY_TITLE} のようにグローバル変数だけ別の記法がありましたが、Twig ではグローバル変数も {{ }} で書けます。記法を使い分ける必要がありません。

{# 標準テンプレートのグローバル変数に相当 #}
<title>{{ ENTRY_TITLE }}</title>
<p>現在のカテゴリーコード:{{ CCD }}</p>

3. 変数の定義

{% set %} でテンプレート内に変数を定義できます。

Twig Playground で試す:

{% set greeting = "こんにちは" %}
{% set name = "山田" %}

<p>{{ greeting }}、{{ name }}さん!</p>

文字列の連結には ~(チルダ)演算子を使います。

Twig Playground で試す:

{% set fullName = "山田" ~ " " ~ "太郎" %}
<p>{{ fullName }}</p>

4. 条件分岐(if)

{% if %} / {% endif %} で条件分岐を書きます。PHP や JavaScript の if と同じ感覚で書けます。

Twig Playground で試す:

{% set isLoggedIn = true %}

{% if isLoggedIn %}
  <p>ログイン中です。</p>
{% else %}
  <p>ログインしてください。</p>
{% endif %}

条件式の記述例

比較演算子

  • ==(等しい)

  • !=(等しくない)

  • >(大きい)

  • <(小さい)

  • >=(以上)

  • <=(以下)

Twig Playground で試す:

{% set age = 18 %}

{% if age >= 18 %}
  <p>成人です。</p>
{% else %}
  <p>未成年です</p>
{% endif %}

論理演算子

  • and(かつ)

  • or(または)

  • not(否定)

Twig Playground で試す:

{% set isLogin = true %}
{% set isAdmin = true %}

{% if isLogin and isAdmin %}
    <p>ログイン済みの管理者です。</p>
{% endif %}

{% if not isLogin %}
    <p>ログインしてください。</p>
{% endif %}

is 演算子

  • empty: 空かどうか

  • defined: 定義されているかどうか

  • null: nullであるかどうか

  • even/odd: 偶数か奇数か

  • divisible by: 指定した数で割り切れるか

Twig Playground で試す:

{% set name = '山田' %}
{% set list = [] %}
{% set number = 6 %}

{% if name is defined %}
    <p>名前: {{ name }}</p>
{% endif %}

{% if list is empty %}
    <p>リストは空です。</p>
{% endif %}

{% if number is even %}
<p>
  {{ number }}は偶数です。
</p>
{% endif %}

{% if number is divisible by(2) %}
  <p>{{ number }}は2で割り切れます。</p>
{% elseif number is divisible by(3) %}
  <p>{{ number }}は3で割り切れます。</p>
{% else %}
  <p>{{ number }}は2でも3でも割り切れません。</p>
{% endif %}

in 演算子

Twig Playground で試す:

{% set colors = ["red", "blue", "green"] %}

{% if "blue" in colors %}
    <p>青色が含まれています。</p>
{% endif %}

他のプログラミング言語と同様に、括弧を使って条件式の優先度を制御できます。



5. ループ(for)

{% for %} / {% endfor %} でリストを繰り返し処理します。

Twig Playground で試す:

{% set fruits = ['Apple', 'Banana', 'Cherry'] %}

<ul>
  {% for fruit in fruits %}
    <li>{{ fruit }}</li>
  {% endfor %}
</ul>

loop — ループ内の特殊変数

ループの中では loop という特殊変数が使えます。「最後の要素以外にセパレーターを入れる」などの処理が、標準テンプレートで使っていた <!-- BEGIN glue --> より直感的に書くことができます。

Twig Playground で試す:

{% set tags = ["新商品", "季節限定", "夏メニュー"] %}

<div>
  {% for tag in tags %}
    #{{ tag }}{% if not loop.last %}、{% endif %}
  {% endfor %}
</div>

よく使う loop 変数をまとめます。

変数

内容

loop.index

現在のインデックス(1始まり)

loop.index0

現在のインデックス(0始まり)

loop.first

最初の要素なら true

loop.last

最後の要素なら true

loop.length

要素の総数


6. 階層データのツリー表示

カテゴリーやメニューなど、親子関係のあるデータを表示する場面はよくあります。親の中に子がネストしたHTML構造を作るには、macro(マクロ)を使った再帰呼び出しが有効です。階層の深さに関係なく同じテンプレートで処理できます。

Twig Playground で試す:

{% set categoryTree = {
  name: "ドリンク",
  children: [
    { name: "コーヒー", children: [] },
    { name: "ティー", children: [] }
  ]
} %}

{% macro renderTree(node) %}
  <li>
    {{ node.name }}
    {% if node.children is not empty %}
      <ul>
        {% for child in node.children %}
          {{ _self.renderTree(child) }}
        {% endfor %}
      </ul>
    {% endif %}
  </li>
{% endmacro %}

<ul>
  {{ _self.renderTree(categoryTree) }}
</ul>

macro(マクロ)について

macro(マクロ)は、HTML を出力する再利用可能な関数です。同じテンプレート内で似たような HTML を何度も書く場合に便利で、include ほど大げさにしたくないときの選択肢になります。

マクロの定義と呼び出し

{% macro 名前(引数) %} ... {% endmacro %} で定義し、同じファイル内では {{ _self.名前(引数) }} で呼び出します。

Twig Playground で試す:

{% macro navItem(label, path) %}
  <li class="nav-item"><a href="{{ path }}">{{ label }}</a></li>
{% endmacro %}

<nav>
  <ul>
    {{ _self.navItem('ホーム', '/') }}
    {{ _self.navItem('お知らせ', '/news') }}
    {{ _self.navItem('お問い合わせ', '/contact') }}
  </ul>
</nav>

同じ <li> の構造を3回書く代わりに、マクロ1つでまとめています。

再帰呼び出し

再帰とは、関数が自分自身を呼び出すことです。マクロも再帰できます。

ツリー構造のデータでは、どのノードも「名前を表示する」「子があれば子をループする」という同じ処理になります。階層が何段になっても同じロジックで扱えるため、再帰が向いています。

処理の流れ:

  1. renderTree(categoryTree) が呼ばれる(ルート「ドリンク」)

  2. 「ドリンク」を <li> で出力し、children が空でなければループ開始

  3. 子「コーヒー」に対して _self.renderTree(child) を呼ぶ

  4. 「コーヒー」を出力。children が空なので再帰せず終了

  5. 子「ティー」に対して同様に _self.renderTree(child) を呼ぶ

  6. 以下、子がなくなるまで繰り返し

終了条件が重要です。{% if node.children is not empty %} で子があるときだけ再帰するため、葉(子のいないノード)に到達すると再帰が止まります。この条件がなければ無限ループになります。

孫やひ孫がいる場合も、同じマクロが「自分の名前を出して、子がいれば子ごとに自分を呼ぶ」を繰り返すだけなので、階層の深さを意識せずに書けます。

別テンプレートのマクロを使う

他のテンプレートで定義したマクロを使うには、import タグで読み込みます。

{% import '_macros.twig' as macros %}
{{ macros.navItem('ホーム', '/') }}

7. フィルター(| パイプ)

値を変換・加工するには |(パイプ)でフィルターをつなぎます。

{# 日付フォーマット #}
{{ entry.date | date('Y年m月d日') }}

{# 文字数で切り詰め(a-blog cms 独自フィルター) #}
{{ entry.summary | trim(200, '...') }}

{# 画像リサイズ(a-blog cms 独自フィルター) #}
{{ entry.path | resizeImg(600) }}

| はいくつでもつなげられます。

{# 先頭から200文字を取り出してから小文字に #}
{{ text | slice(0, 200) | lower }}

Twig Playground で試す:

{% set date = "now" | date('Y年m月d日') %}
<p>今日は {{ date }} です。</p>

7. コメント

{# #} でコメントを書きます。コメントはHTMLとして出力されません。

{# ここはコメントです。HTML出力には含まれません #}
{# TODO: このモジュールのカテゴリーを後で確認する #}

HTML コメント(<!-- -->)はそのままソースに出力されますが、Twig コメントは出力されません。テンプレートへの作業メモにはTwigコメントを使いましょう。


8. インクルード

テンプレートの共通部分(ヘッダー・フッター・カードのHTML断片など)を別ファイルに切り出して再利用するには、include() 関数を使います。

たとえばカードコンポーネントを _card.twig として用意するとします。

{# _card.twig #}
<div class="card">
  <h2>{{ entry.title }}</h2>
  <p>{{ entry.summary | trim(100, '...') }}</p>
</div>

これを別のテンプレートから読み込むには次のように書きます。

{{ include('_card.twig') }}

変数を渡したい場合は第2引数にハッシュで指定します。

{{ include('_card.twig', { entry: featuredEntry }) }}

a-blog cms では、管理ボックスや管理UIのパーツもインクルードで読み込みます。

{# エントリー編集ボックスの読み込み #}
{{ include('admin/entry/action.twig', { entry }) }}

{# モジュール設定ボタンの読み込み #}
{{ include('/admin/module/setting.twig', { moduleInfo: entrySummary.moduleInfo }) }}

これらのファイルはa-blog cms本体が提供しているため、自分で作成する必要はありません。テンプレートの決まった位置に書くだけで管理UIが有効になります。

9. a-blog cms との組み合わせイメージ

Twig の基本構文を把握したところで、a-blog cms のモジュールと組み合わせると次のようになります。実際の実装は次章以降で進めますが、全体のイメージとして確認しておきましょう。

{% set entrySummary = module('V2_Entry_Summary', 'モジュールID名', { bid: BID, cid: CID }) %}
<div>
  <div class="acms-cssgrid acms-g-cols-1 acms-g-cols-md-3">
    {% for entry in entrySummary.items %}
    <div>
      {% if entry.mainImage.path %}
      <img
        src="{{ entry.mainImage.path|resizeImg(360) }}"
        alt="{{ entry.mainImage.alt }}"
        class="acms-img-responsive"
        width="{{ entry.mainImage.width }}"
        height="{{ entry.mainImage.height}}"
        loading="lazy"
        decoding="async"
      >
      {% endif %}
      <h3>{{ entry.title }}</h3>
      <p>{{ entry.summary }}</p>
      <p><a href="{{ entry.url }}" class="acms-btn">詳細をみる</a></p>
    </div>
    {% endfor %}
  </div>
</div>

module は a-blog cms 独自の関数ですが、その中身は完全なTwig構文で書けます。a-blog cms 固有の概念は少し覚えるだけで、制御構文は一般的なTwigの知識がそのまま使えるという構成です。


まとめ

この章でおさえたこと:

  • {{ }} で変数を出力し、{% %} で制御構文を書き、{# #} でコメントを書く

  • グローバル変数も通常の変数と同じ {{ }} 記法で書ける

  • {% if %} / {% for %} は一般的なプログラミング言語と同じ感覚で書ける

  • is empty / is defined / loop.last などの便利な記法がある

  • | フィルターで値を加工する(date()trim()resizeImg() など)

  • {{ include('ファイル名') }} で共通パーツを再利用する

  • Twig Playground で環境なしに構文を試せる

また、チュートリアルでは詳しく扱いませんが、発展情報として以下の内容も参考にしてください

次の章では、サンプルHTMLをa-blog cmsのTwigテーマとして設定します。まだCMS化はしませんが、「既存のHTMLがa-blog cmsを経由して表示される」状態を作ることが最初のゴールです。