PHPでWebアプリ開発!人気テンプレートエンジン「Twig」を使ってみよう

2017/09/01

Claudio Ribeiro

93

Articles in this issue reproduced from SitePoint
Copyright © 2017, All rights reserved. SitePoint Pty Ltd. www.sitepoint.com. Translation copyright © 2017, KADOKAWA ASCII Research Laboratories, Inc. Japanese syndication rights arranged with SitePoint Pty Ltd, Collingwood, Victoria,Australia through Tuttle-Mori Agency, Inc., Tokyo

PHPを使った本格的なWebアプリ開発に欠かせないのが、テンプレートエンジン。セキュリティ対策やデバッグがしやすく、人気のあるテンプレートエンジン「Twig」を紹介します。

TwigはPHP向けのテンプレートエンジンです。PHP自体がもともとテンプレートエンジンとして始まりましたが、たとえば「Hello world」を書くとしたら、どちらがいいでしょうか?

素のPHP
<?php echo "<p> Hello " . $name . "</p>"; ?>
もしくは
<p> Hello {{ name }} </p>

PHPは冗長な言語です。HTML要素を出力するとより冗長になります。最近のテンプレートシステムは冗長性をなくし、さらに、セキュリティやデバッグといった機能が特徴です。

今回はTwigを取り上げます。

Twig logo

TwigはBlackfireSymfonyを開発した企業Sensio labsが開発しました。Twigの強みと実際の使い方を紹介します。

インストール

Twigのインストール方法は2種類あります。公式Webサイト上の.tar.gz形式のファイルを使うか、Composerが使えます。よく使われているComposerがおすすめです。

composer require twig/twig

PHPやComposerを利用する環境が整っていないならHomestead Improvedがオススメです。5分で同じ開発環境を作れます。

Twigはフロントエンドとバックエンドの両方で使えるテンプレートエンジンです。テンプレートデザイナー向けと開発者向けの2種類の使い方ができます。

一方ではデータを用意し、もう一方はデータを適切な形に変換します。

基本的な使い方

Twigの使い方を説明するために、簡単なプロジェクトを作ります。はじめにTwigを自動起動します。bootstrap.phpファイルを作り以下のコードを書きます。

<?php

// Load our autoloader
require_once __DIR__.'/vendor/autoload.php';

// Specify our Twig templates location
$loader = new Twig_Loader_Filesystem(__DIR__.'/templates');

 // Instantiate our Twig
$twig = new Twig_Environment($loader);

Twigはオブジェクト「Environment」を使います。このクラスのインスタンスには、設定、拡張機能、ファイルシステムやほかの場所から読み込んだテンプレートを格納します。

Twigのインスタンスを自動起動すると、index.phpファイルがほかのデータを読み込んだ場所に作成され、Twigテンプレートに渡されます。

<?php

require_once __DIR__.'/bootstrap.php';

// Create a product list
$products = [
    [
        'name'          => 'Notebook',
        'description'   => 'Core i7',
        'value'         =>  800.00,
        'date_register' => '2017-06-22',
    ],
    [
        'name'          => 'Mouse',
        'description'   => 'Razer',
        'value'         =>  125.00,
        'date_register' => '2017-10-25',
    ],
    [
        'name'          => 'Keyboard',
        'description'   => 'Mechanical Keyboard',
        'value'         =>  250.00,
        'date_register' => '2017-06-23',
    ],
];

// Render our view
echo $twig->render('index.html', ['products' => $products] );

テンプレートで使う商品(products)を配列にした簡単な例です。render()メソッドで、テンプレートの名前(事前に定義したものがテンプレートフォルダーに入っています)とデータをテンプレートに送ります。

この例を完成させるために、/templatesフォルダーにindex.htmlファイルを作ります。以下がテンプレートです。

<!DOCTYPE html>
<html lang="pt-BR">
    <head>
        <meta charset="UTF-8">
        <title>Twig Example</title>
    </head>
    <body>
    <table border="1" style="width: 80%;">
        <thead>
            <tr>
                <td>Product</td>
                <td>Description</td>
                <td>Value</td>
                <td>Date</td>
            </tr>
        </thead>
        <tbody>
            {% for product in products %}
                <tr>
                    <td>{{ product.name }}</td>
                    <td>{{ product.description }}</td>
                    <td>{{ product.value }}</td>
                    <td>{{ product.date_register|date("m/d/Y") }}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
    </body>
</html>

index.phpをブラウザーで開くと(ホストとサーバーのセットアップの仕方によって、localhostかhomestead.appにアクセスするか決まります)、次のように表示されます。

Rendered table

もう一度テンプレートのコードを見ましょう。

2種類のデリミタがあります。{{ ... }}は表示や操作の結果を表し、{% ... %}は分岐やforループといった制御構文に使います。こうしたデリミタはTwigにおける基本的な文法要素で、Twigはテンプレートに独自の要素として処理するよう「通知」します。

レイアウト

テンプレートでヘッダーやフッターなどの要素の繰り返しを避けるため、Twigはテンプレートの中にテンプレートをネストできます。これらをブロックと呼びます。

さきほどの例からHTMLの定義要素を抜き出して説明します。新しいHTMLファイル「layout.html」を作ります。

<!DOCTYPE html>
<html lang="pt-BR">
    <head>
        <meta charset="UTF-8">
        <title>Tutorial Example</title>
    </head>
    <body>
        {% block content %}
        {% endblock %}
    </body>
</html>

ブロック「content」を作りました。layout.htmlが展開したテンプレートは、その場所に記述されたcontentブロックの内容を表示します。同じレイアウトを何度も書かずに使い回せます。この場合のindex.htmlファイルは以下の通りです。

{% extends "layout.html" %}

{% block content %}
    <table border="1" style="width: 80%;">
        <thead>
            <tr>
                <td>Product</td>
                <td>Description</td>
                <td>Value</td>
                <td>Date</td>
            </tr>
        </thead>
        <tbody>
            {% for product in products %}
                <tr>
                    <td>{{ product.name }}</td>
                    <td>{{ product.description }}</td>
                    <td>{{ product.value }}</td>
                    <td>{{ product.date_register|date("m/d/Y") }}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
{% endblock %}

Twigはシングルブロックのレンダリングもできます。テンプレートを読み込みブロックをレンダリングします。

$template = $twig->load('index.html');
echo $template->renderBlock('content', array('products' => $products));

文章のブロックごとに分けることで、複雑さを減らして同じページを記述します。

キャッシュ

Environmentオブジェクトにはテンプレートを読み込む以上の役割があります。

cacheオプションを直接変更しなければ、Twigは、リクエストのたびにテンプレートの構文解析をしないため、コンパイルしたテンプレートをキャッシュします。コンパイルしたテンプレートは事前に準備したフォルダに保存します。キャッシュはあくまでもコンパイルしたテンプレートを格納するだけで、評価はしません。テンプレートの評価は都度必要です。

bootstrap.phpファイルを書き換えて、テンプレートをキャッシュします。

$twig = new Twig_Environment($loader, ['cache' => '/templates/cache']);

ループ構文

ループ構文は、配列内の要素に対して、forタグと割り当てたエイリアスを使います。今回の例では、エイリアス「product」を割り当てて、products配列に使っています。.演算子で配列内の各要素にアクセスします。endforタグでループ構文の終わりを示します。

..演算子により、数列や文字列を使ったループ構文が書けます。

{% for number in 0..100 %}
     {{ number }}
{% endfor %}
文字列の場合
{% for letter in 'a'..'z' %}
     {{ letter }}
{% endfor %}

range機能の糖衣構文で、ネイティブPHPのrange機能と同じような働きをします。

ループ構文に条件をつけると要素の中から繰り返したいものだけを選べるので便利です。商品(products)の要素の中から、価格(value)が250より小さいものを選びます。

<tbody>
    {% for product in products if product.value < 250 %}
        <tr>
            <td>{{ product.name }}</td>
            <td>{{ product.description }}</td>
            <td>{{ product.value }}</td>
            <td>{{ product.date_register|date("m/d/Y") }}</td>
    </tr>
    {% endfor %}
</tbody>

条件分岐

Twigにはif、elseif、if not、elseのタグを使った条件分岐が用意されています。ほかのプログラミング言語と同様、テンプレート内で条件に応じて要素を振り分けます。

商品(products)の中から価格(value)が500より高いものを表示します。

<tbody>
    {% for product in products %}
    {% if product.value > 500 %}
                <tr>
                    <td>{{ product.name }}</td>
                    <td>{{ product.description }}</td>
                    <td>{{ product.value }}</td>
                    <td>{{ product.date_register|date("m/d/Y") }}</td>
        </tr>
    {% endif %}
    {% endfor %}
</tbody>

フィルター

いろいろなフォーマットのフィルターで、テンプレートに渡す情報を選別します。よく使われる重要なフィルターを紹介します。Twigフィルターはここを参照してください。

Dateフィルターとdate_modifyフィルター

dateフィルターは与えられたフォーマットに従ってデータを変換します。

<td>{{ product.date_register|date("m/d/Y") }}</td>

日付けのデータをmonth/day/yearの形式で表示します。dateフィルターに加えて、date_modifyフィルターを使うと連続して日付けを変換します。日を進めたい場合は以下の通りです。

<td>{{ product.date_register|date_modify("+1 day")|date("m/d/Y") }}</td>

Formatフィルター

Formatフィルターはプレースホルダーを与えられた文字列に置き換えます。

<td>{{ "This product description is: %s"|format(product.description) }}</td>

Striptagsフィルター

striptagsフィルターはSGML/XMLタグを取り除き、隣接する空白を1つのスペースに置き換えます。

{{ <p>Hello World</p>|striptags }}`

Escapeフィルター

Escapeフィルターは文字列を安全に表示できる重要なフィルターの1つです。デフォルトではHTMLのエスケープ規則に則っているので、

{{ products.description|escape }}

上記の記述は、

{{ products.description|escape('html') }}

と同じ意味です。

js、CSS、URL、html_attrもエスケープ規則が適用されます。Javascript、CSS、URI、HTMLに属するエスケープ文字です。

デバッグ

最後にデバッグについて取り上げます。テンプレートが利用する全情報にアクセスするためにTwigにはdump()機能があります。デフォルトでは利用できません。Twigの環境を構築する際にTwig_Extension_Debugを追加します。

$twig = new Twig_Environment($loader, array('debug' => true));
$twig->addExtension(new Twig_Extension_Debug());

上記の処理で、デバッグ情報が実稼働サーバーに漏れる心配はありません。設定が終わればdump()機能を実行するだけでテンプレートが利用する全情報をダンプできます。

{{ dump(products) }}

最後に

Twigの基礎をしっかり固めて、ぜひ実際の作業に役立ててください。Twigについてさらに深く知りたいなら、公式Webサイトのドキュメントやリファレンスも参照してください。

テンプレートエンジンを使っていますか? Twigはどう感じましたか? BladeやSmartyといった有名なテンプレートエンジンと比べてみてください。

本記事はWern Anchetaが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者のみなさんに感謝します。

(原文:Twig – the Most Popular Stand-Alone PHP Template Engine

[翻訳:前田類/編集:Livit

Copyright © 2017, Claudio Ribeiro All Rights Reserved.

Claudio Ribeiro

Claudio Ribeiro

リスボン在住のソフトウェア開発者で、旅行者、ライターでもあります。Fixeadsで開発に取り組むほか、バックパックを背負って世界のどこかにいるか、複雑すぎて理解できないようなフレームワークをいじっています。

Loading...