a-blog cms での Twig の書き方を体験してみよう!
このハンズオン記事では、Ver. 3.2 で追加されるTwigテンプレートを活用する方法を解説します。
TwigはPHPでのテンプレートエンジンとして広く使われており、シンプルかつ強力なテンプレート機能を提供します。 Twigの基本からa-blog cmsのテンプレートでの利用方法ついて実践的に学びましょう。
Twigとは?
Twigは、PHPでよく使用されるテンプレートエンジンで、読みやすく保守しやすいコードを可能にします。 また、テンプレートとロジックを分離できるため、フロントエンドとバックエンドの開発を分けやすいのが特徴です。
Twigが使用されているCMSは以下のようなものがあります。
- Drupal
- Craft CMS
- EC-CUBE
幅広く利用されているため、Twigに関する記事や、触った経験がある人も多く、a-blog cms でカスタマイズするハードルが下がることが期待できます。
a-blog cms で Twigテンプレートを利用可能にする
Ver. 3.2 で Twigテンプレートに対応しますが、従来のテンプレート記法も使用することができます。 Twigと従来のテンプレート記法のどちらかを選択するということではなく、従来のテンプレート記法とTwig記法を混ぜてテンプレートをかけるようになります。
Twig記法を有効にする
「管理画面 > テーマ」から、使用テーマの編集画面に入り、以下スクショのように「Twigテンプレート」を有効にすることで、Twig記法が利用することができるようになります。
もしくは、テーマ内の「template.yaml」を以下のように設定することでも可能です。
tpl_top: xxxxxxx
tpl_index: xxxxxxx
tpl_detail: xxxxxxx
tpl_404: xxxxxxx
tpl_admin: xxxxxxx
tpl_entry_edit: xxxxxxx
tpl_entry_add: xxxxxxx
tpl_login: xxxxxxx
tpl_twig: enabled # この行を追加しTwigを有効化
テスト用のテンプレートを作成してみましょう
試しながら進めることが出来るようにテスト用のテンプレートを用意しましょう。
ご利用のテーマを「テーマ設定」から Twigを有効にしていただいたあと、テーマに「test.html.twig」を用意します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Twigテスト</title>
</head>
<body>
<h1>Twigのテスト</h1>
</body>
</html>
以下URLでこのテンプレートを表示できます。
https://example.com/test.html
テンプレートの拡張子の付け方については、後ほど解説します。
変数の書き方
従来の記法
従来の記法では、変数を中括弧({変数})で囲むことで出力できます。
<p>{name}</p>
Twigの記法
Twigでは、変数をダブル中括弧({{ 変数 }})で囲むことで出力できます。(Twig標準の仕様に従います)
<p>{{ name }}</p>
グローバル変数の書き方
従来の記法
従来の記法では、変数を%中括弧(%{変数})で囲むことで出力できます。
<h1>%{BLOG_NAME}</h1>
Twigの記法
Twigでは、グローバル変数と変数の記法に区別はありません。ダブル中括弧({{ 変数 }})で囲むことで出力できます。Twig標準の仕様に従います。
<h1>{{ BLOG_NAME }}</h1>
従来の記法では、グローバル変数に校正オプションを適応できませんでしたが、Twigでは変数と同じ記法になるので、グローバル変数に校正オプションも同様に適応できるようになります。
変数を定義する
Twig内で変数を定義することができます。変数定義には「setタグ」を使います。例えば、計算や文字列の組み立てなどに使用できます。 従来の記法では、SetTemplateやSetRenderedが近いですが、変数として利用できる点で違った機能になり、従来テンプレートには同様の機能はありません。(Twig標準の仕様に従います)
Twigの記法
{% set greeting = "こんにちは" %}
{% set name = "山田" %}
<p>{{ greeting }}, {{ name }}さん!</p>
文字列の連結
Twigでの「~」は、文字列を結合するために使用される演算子です。PHPでの「.」やJavaScriptでの「+」に相当し、文字列と他の変数を簡単に結合できます。
{% set title = "今日の天気" %}
{% set weather = "晴れ" %}
<p>{{ title }} は {{ weather }}です。</p>
<p>{{ title ~ "は" ~ weather ~ "です。" }}</p> <!-- 連結して出力 -->
校正オプション(フィルター)の書き方
従来の記法
従来の記法では、変数の後ろに大括弧({変数}[校正オプション1|校正オプション2])を追加して校正オプションを指定します。複数指定する場合は、パイプで繋ぎます。
<p>{content}[nl2br|safe_html]</p>
* safe_htmlは、Ver. 3.2 で追加される校正オプションです。危険なタグ以外の安全なHTMLを出力します。
Twigの記法
Twigの記法では、変数の中にパイプ(|)で区切りフィルターを指定します({{ 変数|フィルター1|フィルター2 }})(Twig標準の仕様に従います)
{% set content = '今日の天気は\n晴れです' %}
<p>{{ content|nl2br|safe_html }}</p>
使用できる校正オプション(フィルター)の種類
- a-blog cms 標準の校正オプション
- a-blog cms で拡張(Hook)した校正オプション
- Twig 標準のフィルター: https://twig.symfony.com/doc/3.x/filters/index.html
従来のa-blog cmsの校正オプションに加え、Twig標準のフィルターも全く同じ記法で指定することができます。
{% set sizes = [34, 36, 38, 40, 42] %}
{{ sizes|filter(v => v > 38)|join(', ') }}
{# output 40, 42 #}
引数に変数を指定
従来の校正オプションでは、一部校正オプションのみ引数に変数を利用できましたが、Twig記法だと全てのフィルターに変数が利用できます。
{% set content = '文書を途中で切ります' %}
{% set length = 4 %}
{% set ellipsis = '...' %}
<p>
{{ content|mb_trim(length, ellipsis) }}
</p>
フィルターを使用したデフォルト値
変数の値がなかった場合に、デフォルト値を出力することができます。
<p>{{ nickname | default("ゲスト") }}</p>
twigでの「raw」校正オプション(フィルター)の仕様が変更になります。
既存テンプレート
<!-- 危険なタグはエスケープされ、安全なHTMLは出力されます -->
{hoge}[raw]
<!-- scriptを含めエスケープを無効にして、値をそのまま出力します。使用箇所には注意が必要です。 -->
{hoge}[raw|allow_dangerous_tag]
Twigテンプレート
<!-- 危険なタグはエスケープされ、安全なHTMLは出力されます -->
{{ hoge|safe_html }}
<!-- scriptを含めエスケープを無効にして、値をそのまま出力します。使用箇所には注意が必要です。 -->
{{ hoge|raw }}
if文の書き方
従来の記法
従来の記法では、専用のコメントでif文を記述します。
<!-- BEGIN_IF [%{SESSION_USER_ID}/nem] -->
<p>
ログイン中です
</p>
<!-- END_IF -->
Twigの記法
Twigでは「ifタグ」を使用して記述します。(Twig標準の仕様に従います)
{% if SESSION_USER_AUTH %}
<p>
ログイン中です
</p>
{% endif %}
条件式の記述例
比較演算子
- ==(等しい)
- !=(等しくない)
- >(大きい)
- <(小さい)
- >=(以上)
- <=(以下)
{% set age = 18 %}
{% if age >= 18 %}
<p>成人です。</p>
{% else %}
<p>未成年です</p>
{% endif %}
論理演算子
- and(かつ)
- or(または)
- not(否定)
{% 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: 指定した数で割り切れるか
{% 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 演算子
{% set colors = ["red", "blue", "green"] %}
{% if "blue" in colors %}
<p>青色が含まれています。</p>
{% endif %}
他のプログラミング言語と同様に、括弧を使って条件式の優先度を制御できます。
モジュールの呼び出し方
従来の記法
従来の記法では、専用のコメント文で囲んだ範囲にモジュールの変数を展開します。
<!-- BEGIN_MODULE Entry_Summary id="xxxxx" ctx="bid/1" -->
<p>{indexBlogName}<p>
<!-- END_MODULE Entry_Summary -->
Twigの記法
Twigを拡張し、a-blog cms独自の関数を追加しています。関数で呼び出したモジュールのデータを変数に格納して使用します。
{% set hoge = module('V2_Entry_Summary', 'xxxxx', 'bid/1') %}
<p>{{ hoge.indexBlogName }}</p>
* 「モジュールID」と「URLコンテキスト」は、従来モジュール同様に省略可能です。
{% set hoge = module('V2_Entry_Summary') %}
Twig記法では従来のモジュールは呼び出せないのでご注意ください。Twigで呼び出せるモジュールはモジュール名の最初が「V2」から始まるモジュールのみになります。
Ver. 3.2.0-alpha 版では一部モジュールのみV2モジュールに対応しています。現状Twigで使用できるモジュールは以下となります。
- V2_Entry_Summary
- V2_Entry_Body
- V2_Entry_TagRelational
- V2_Category_Tree
- V2_Media_Banner
- V2_Blog_Field
- V2_Module_Field
- V2_Links
- V2_Tag_Filter
- V2_Tag_Cloud
- V2_Topicpath
- V2_User_Profile
- V2_Ogp
名前が似ていても従来のモジュールとは出力される変数や構造は違いますのでご注意ください。
タッチモジュールの呼び出し方
従来の記法
従来の記法では、専用のコメント文で囲んだ範囲の表示・非表示を制御します。
<!-- BEGIN_MODULE Touch_Login -->
<p>ログイン中です</p>
<!-- END_MODULE Touch_Login -->
Twigの記法
Twigを拡張し、a-blog cms独自の関数を追加しています。関数で呼び出すことで真偽値が返却されるので、if記法と併用して出し分けます。
{% if touch('Touch_Login') %}
<p>
ログイン中です
</p>
{% endif %}
touch関数は、module関数と違い既存のすべてのタッチモジュールを呼び出せます。
表示・非表示を制御しているのは、単なる「ifタグ」なので、条件式をtouch関数だけではなく組み合わせて利用することもできます。
{% if touch('Touch_Login') and IS_ADMIN %}
<p>ログイン中 かつ 管理ページの場合<p>
{% endif %}
ループ処理の書き方
従来の記法
従来の記法では、モジュールが出力するループブロックで囲んだ範囲がループして出力されます。
<!-- BEGIN_MODULE Entry_Summary -->
<ul>
<!-- BEGIN entry:loop -->
<li>{title}</li>
<!-- END entry:loop -->
</ul>
<!-- END_MODULE Entry_Summary -->
Twigの記法
Twigでのループの書き方はとてもシンプルです。「forタグ」を使って、配列やリストを簡単にループ処理できます。基本的な構文は以下の通りです。(Twig標準の仕様に従います)
{% set fruits = ['Apple', 'Banana', 'Cherry'] %}
<ul>
{% for fruit in fruits %}
<li>{{ fruit }}</li>
{% endfor %}
</ul>
モジュールで取得したデータをループする例
{% set entrySummary = module('V2_Entry_Summary') %}
<ul>
{% for entry in entrySummary.entries %}
<li>{{ entry.title }}</li>
{% endfor %}
</ul>
インデックスの取得
「loop」という特殊な変数を使用して、ループのインデックスやその他の情報を取得できます。loop.indexで1から始まるインデックスを取得したり、loop.index0で0から始まるインデックスを取得できます。
<ul>
{% for entry in entrySummary.entries %}
<li>{{ loop.index }}: {{ entry.title }}</li>
{% endfor %}
</ul>
その他のloopオプション
- loop.first:ループの最初の要素の場合にtrueを返す
- loop.last:ループの最後の要素の場合にtrueを返す
- loop.revindex:残りの要素数(1からカウント)
- loop.revindex0:残りの要素数(0からカウント)
「loop.first」や「loop.last」を使用することで、従来の <!-- BEGIN glue -->
の代替ができます。
{% for entry in entrySummary.entries %}
{{ entry.title }}
{% if not loop.last %} | {% endif %}
{% endfor %}
インクルードの書き方
Twigでのインクルードの書き方について解決します。(Twig標準の仕様に従います)
基本的なインクルード
include関数を使ってテンプレートをインクルードします。
{{ include('/path/to/parts.twig') }}
変数を渡す
インクルードするテンプレートに変数を渡したい場合は、第2引数として変数を渡します。
{{ include('/path/to/parts.twig', { title: 'タイトルです' }) }}
このように書くことで、/path/to/parts.twig
内でtitle変数が使えるようになります。
ファイルが存在しない場合のエラー回避
従来のインクルード文だと、インクルードするファイルがない場合でもエラーは発生しませんでしたが、Twigのインクルードはファイルが存在しない場合エラーが出力されます。
インクルードするファイルが存在しない場合にエラーを回避するには、第3引数にtrueを渡してignore_missingオプションを有効にします。
{{ include(/path/to/parts.twig', {}, true) }}
or
{{ include('/path/to/parts.twig', {}, ignore_missing = true) }}
インクルード文の中にはグローバル変数以外にも全ての変数が利用できます。
従来のインクルードだと、インクルードパス内に利用できる変数はグローバル変数かつ特定のものに制限されていましたが、Twigのインクルードはすべての変数をインクルードパスに含めることが可能です。
{% set filename = 'hoge' %}
{{ include('/include/' ~ filename ~ '.twig', ignore_missing = true) }}
テンプレート継承の書き方
Twigでも従来テンプレートと同じようにテンプレートの継承機能が利用できます。Twig標準の仕様に従っているため、ここでは詳しく説明しません。詳しくはTwigのドキュメントを参照ください。
Twigのテンプレート継承の例
継承元テンプレート
<!DOCTYPE html>
<html>
<head>
{% block headMeta %}
{{ include('/include/head/meta.twig') }}
{% endblock %}
{% block headLink %}
{{ include('/include/head/link.twig') }}
{% endblock %}
{% block headJs %}
{{ include('/include/head/js.twig') }}
{% endblock %}
</head>
<body>
<header>
<h1>ヘッダー</h1>
</header>
<main>
{% block main %}
<p>継承してメインコンテンツを設定してください</p>
{% endblock %}
</main>
<footer>
{% block footer %}
<p>Site Footer</p>
{% endblock %}
</footer>
</body>
</html>
継承したテンプレート
{% extends 'parent_template.twig' %}
{% block headLink %}
{{ parent() }}
<link rel="stylesheet" href="/include/edit/custom.css" />
{% endblock %}
{% block main %}
<p>
メインコンテンツを上書き
</p>
{% endblock %}
Twigのブロック名にはいくつかの命名規則があります
- 英字(アルファベット)と数字、アンダースコア(_)のみが使用可能
- 英字から始めなければならない
有効なブロック名の例
- headJs
- head_js
- headJs123
無効なブロック名の例
- head-js
- 123headJs
テンプレートの解決順
Twigが有効なテーマの場合、以下の順序でテンプレートが解決されます。
- インクルードなどを含め、Twigによる記述が解決される
- Twigが解決されたあとのテンプレートを従来のテンプレート記法部分が解決される
順番に解決される特性上、テンプレートが解決されないパターンがありますので、いくつかパターンを紹介します。
テンプレートが解決されないパターン
- 従来の
@include
で読み込んだテンプレートにあるTwig記法は解決されない - 従来の
@extends
で継承したテンプレート以下にあるTwig記法は解決されない - 従来の
@extends
で継承されているテンプレートは、Twig記法でインクルード出来ない
Twigテンプレートの拡張子
テンプレートの拡張子について解説します。
拡張子は「twig」がおすすめ
特に拡張子に縛りはありません。そのため、拡張子が .html
のテンプレートにTwigの記法で記述しても正しく動作します。
ただし、Twigで記述する場合は拡張子を .twig
にすることをおすすめします。理由は、エディタ側が編集中のテンプレートをTwigとして認識することで、ハイライト表示やTwigフォーマットへの対応など、エディタの便利な機能を活用できるからです。
URLでテンプレートを指定する場合
動的テンプレートやインクルードファイルでは、テンプレートファイル名をURLで指定することがないため、特に意識する必要はありません。 しかし、URLでテンプレートを直接指定する場合は、少し工夫が必要です。
たとえば、検索結果ページを https://example.com/search.html
で表示するケースがあると思います。
このとき、テンプレートファイル名を search.twig
にしてしまうと、URLが https://example.com/search.twig
になってしまいます。
これを避けるために、テンプレートファイル名を search.html.twig
にします。
URLで指定されたファイル名に対して、自動的に .twig
拡張子のテンプレートも検索する機能があるため、URLとファイル名が異なっていても、search.html.twig
のテンプレートが正しく表示される仕組みになっています。
モジュール変数の確認方法
現状のα版パッケージでは、Twig対応のV2モジュールの変数表やスニペットが用意されていないため、どのような変数があるか分かりません。そこで以下方法でモジュールの出力内容を確認できるようにします。
ご利用テーマの直下に「module.json.twig」を用意します。
{{ module('V2_Entry_Summary')|jsonPrint|raw }}
表示したいモジュール名を指定します。「jsonPrint」フィルターでJSONに変換させ、モジュールが持っているデータを見えやすく表示します。
あとは、以下URLでモジュールの内容を表示できます。
https://example.com/module.json
オブジェクト・配列へのアクセス方法
慣れていないと、モジュールなどで取得した複雑なデータへのアクセスが難しいので、ここで学習と練習をしましょう。
オブジェクトへのアクセス
オブジェクトとは、キーと値のペアを持つデータの集合になります。
{
key1: 'key1の値',
key2: 'key2の値'
}
オブジェクトの値にアクセスするには、ドット記法またはブラケット記法を使用します。
- ドット記法:object.property
- ブラケット記法:object["property"]
{% set user = { name: '山田', age: 18 } %}
{{ user.name }} {# 山田 #}
{{ user["age"] }} {# 18 #}
配列へのアクセス
配列は、データを順番に並べて管理するためのデータ構造です。
["りんご", "バナナ", "みかん"]
配列の要素には、インデックス番号でアクセスします。Twigでは、インデックスは0から始まります。
{% set fruits = ['りんご', 'バナナ', 'みかん'] %}
{{ fruits[0] }} {# りんご #}
{{ fruits[2] }} {# みかん #}
また配列はループで回すことができます。
{% set fruits = ['りんご', 'バナナ', 'みかん'] %}
{% for fruit in fruits %}
{{ fruit }}
{% endfor %}
練習問題
以下コードをテンプレートにはり、以下問題に挑戦してみてください。
{% set data = {
title: 'テストデータ',
fruits: ['りんご', 'バナナ', 'みかん'],
users: [
{ name: '山田', age: '18', address: { city: '東京都', postalCode: '100-0005' } },
{ name: '佐藤', age: '30', address: { city: '大阪府', postalCode: '530-0001' } },
{ name: '鈴木', age: '45', address: { city: '愛知県', postalCode: '460-0002' } }
]
} %}
問題例 2番目「fruits」を表示してください。
{{ data.fruits[1] }} {# バナナ #}
問題1 最初の果物の名前を表示してください。(「りんご」が出力されればOK)
問題2 2番目のユーザーの名前を表示してください。(「佐藤」が出力されればOK)
問題3 最後のユーザーの住所(city)を表示してください。(「愛知県」が出力されればOK)
問題4 for文を使って果物を全て表示してください。(「りんご、バナナ、みかん」が出力されればOK)
問題5 for文を使って各ユーザーの名前と住所を以下の形式で表示してください。
山田 - 東京都
佐藤 - 大阪府
鈴木 - 愛知県
その他のTwig機能について
a-blog cms での Twig の基本的な使い方について学んできましたが、他にも便利なTwigの機能がありますので、ぜひ目を通してみてください。
たとえば「V2_Category_Tree」モジュールはデータが以下のように階層構造になっており、 階層すべてを表示しようと思うと、階層分の「for文」をテンプレートに書く必要があります。
V2_Category_Treeのデータ構造例 (一部データを省略しています)
{
categories:
[
{
id: 1,
name: "お知らせ",
},
{
id: 2,
name: "製品",
children:
[
{
id: 3,
name: "家庭用製品",
},
{
id: 4,
name: "業務用製品",
children:
[
{
id: 6,
name: "業務用製品のお知らせ",
},
],
},
],
},
]
}
for文のみを使用したテンプレート例
{% set tree = module('V2_Category_Tree') %}
<ul>
{% for category in tree.categories %}
<li>
{{ category.name }}
{% if category.children is not empty %}
<ul>
{% for child in category.children %}
<li>
{{ child.name }}
{% if child.children is not empty %}
<ul>
{% for child2 in child.children %}
<li>{{ child2.name }}</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
これをTwigの「macro(マクロ)機能」を使うことで、シンプルに記述することができます。
macroを使用したテンプレート例
{% set tree = module('V2_Category_Tree') %}
{% macro renderCategories(categories) %}
<ul>
{% for category in categories %}
<li>
{{ category.name }}
{% if category.children is not empty %}
{{ _self.renderCategories(category.children) }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}
{{ _self.renderCategories(tree.categories) }}
色々触ってみましょう!
以上でTwig機能の解説は終わりになります。お疲れ様でした!
残り時間は、この記事で学んだことを実際に色々と試してみてください。既存テーマを一部Twigで書き直してみるとより理解が進むと思います。
配布しているα版パッケージのテーマに「twig@blog」という「blog」テーマをTwigで書き直したものも同梱されていますので、ぜひ参考ください。