2012年2月24日

Yii Framework: PHP用テンプレートエンジンTwigを使ったコードサンプル

Twig に関しては こちら を参考にしてください。
Yii で Twig を利用するにあたって、はじめは twig-view-renderer を使っていたのですが、yiiext / twig-renderer のほうがバージョンが新しいこともあって、こちらを使うことにしました。

DL 後 ETwigViewRenderer.php を protected/extensions 下に設置。次に fabpot / Twig から最新の Twig を DL して、lib 下の Twig ディレクトリをまるごと protected/vendors 下に移動。

今回使ったそれぞれのバージョンは以下になります。
  • Twig view renderer - v1.1.2
  • Twig - v1.6.0

あとは protected/config/main.php で諸々の設定をしてあげます。Twig view renderer の README や ETwigViewRenderer.php の中身を見ると設定の例が載っています。

とりあえず Twig 自体触りたてなのと、Yii で Twig を使った情報がほとんどなかったので、わからない箇所が多々ありましたが、デフォルトの layouts/column1.php, column2.php, main.php と GiiのCrud Generator で生成したものを Twig に変換したものが以下になります。
{% extends 'views/layouts/main.twig' %}
{% block container %}
<div class="container">
<div id="content">
{% block content %}
{{ content }}
{% endblock content %}
</div><!-- /#content -->
</div><!-- /.container -->
{% endblock container %}
view raw column1.twig hosted with ❤ by GitHub
{% extends 'views/layouts/main.twig' %}
{% block container %}
<div class="container">
<div class="span-19">
<div id="content">
{% block content %}
{{ content }}
{% endblock content %}
</div><!-- ./content -->
</div><!-- /.span-19 -->
<div class="span-5 last">
<div id="sidebar">
{% do this.beginWidget('zii.widgets.CPortlet', {
'title': 'Operations',
}) %}
{% do this.widget('zii.widgets.CMenu', {
'items': this.menu,
'htmlOptions': {'class': 'operations'},
}) %}
{% do this.endWidget %}
</div><!-- /.sidebar -->
</div><!-- /.span-5 last -->
</div><!-- /.container -->
{% endblock container %}
view raw column2.twig hosted with ❤ by GitHub
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="language" content="ja" />
<!-- blueprint CSS framework -->
<link rel="stylesheet" type="text/css" href="{{ App.baseUrl }}/css/screen.css" media="screen, projection" />
<link rel="stylesheet" type="text/css" href="{{ App.baseUrl }}/css/print.css" media="print" />
<!--[if lt IE 8]>
<link rel="stylesheet" type="text/css" href="{{ App.baseUrl }}/css/ie.css" media="screen, projection" />
<![endif]-->
<link rel="stylesheet" type="text/css" href="{{ App.baseUrl }}/css/main.css" />
<link rel="stylesheet" type="text/css" href="{{ App.baseUrl }}/css/form.css" />
<title>{{ this.pageTitle|e }}</title>
</head>
<body>
<div class="container" id="page">
<div id="header">
<div id="logo">{{ App.name|e }}</div>
</div><!-- /#header -->
<div id="mainmenu">
{% do this.widget('zii.widgets.CMenu', {
'items': [
{'label': 'Home', 'url': ['/hoge/index']},
{'label': 'About', 'url': ['/site/about']},
{'label': 'Contact', 'url': ['/site/contact']},
{'label': 'Login', 'url': ['/site/login'], 'visible': App.user.isGuest},
{'label': 'Logout ('~App.user.name~')', 'url': ['/site/logout'], 'visible': not App.user.isGuest},
{'label': 'Gii', 'url': ['/gii']},
],
}) %}
</div><!-- /#mainmenu -->
{% if this.breadcrumbs %}
{% do this.widget('zii.widgets.CBreadcrumbs', {
'links': this.breadcrumbs,
}) %}
{% endif %}
{% block container %}
{% endblock container %}
<div id="footer">
Copyright &copy; {{ now|date('Y') }} by My Company.<br/>
All Rights Reserved.<br/>
{{ Yii.powered }}
</div><!-- /#footer -->
</div><!-- /.container #page -->
</body>
</html>
view raw main.twig hosted with ❤ by GitHub
{% do this.setBreadcrumbs(['Hoges']) %}
{% do this.setMenu([
{'label': 'Create Hoge', 'url': ['create']},
{'label': 'Manage Hoge', 'url': ['admin']},
]) %}
<h1>Hoges</h1>
{% do this.widget('zii.widgets.CListView', {
'dataProvider': dataProvider,
'itemView': '_view',
}) %}
view raw index.twig hosted with ❤ by GitHub
<div class="view">
<b>{{ data.getAttributeLabel('id')|e }}</b>
{{ C.Html.link(data.id|e, {0: 'view', 'id': data.id}) }}
<br />
<b>{{ data.getAttributeLabel('userid')|e }}</b>
{{ data.userid|e }}
<br />
<b>{{ data.getAttributeLabel('fuga')|e }}</b>
{{ data.fuga|e }}
<br />
<b>{{ data.getAttributeLabel('piyo')|e }}</b>
{{ data.piyo|e }}
<br />
</div><!-- /.view -->
view raw _view.twig hosted with ❤ by GitHub
{% do this.setBreadCrumbs({
'Hoges': ['index'],
0: 'Manage',
}) %}
{% do this.setMenu([
{'label': 'List Hoge', 'url': ['index']},
{'label': 'Create Hoge', 'url': ['create']},
]) %}
{{ void(App.clientScript.registerScript('search', "
$('.search-button').click(function(){
$('.search-form').toggle();
return false;
});
$('.search-form form').submit(function(){
$.fn.yiiGridView.update('hoge-grid', {
data: $(this).serialize()
});
return false;
});
")) }}
<h1>Manage Hoges</h1>
<p>
You may optionally enter a comparison operator (<b>&lt;</b>, <b>&lt;=</b>, <b>&gt;</b>, <b>&gt;=</b>, <b>&lt;&gt;</b>
or <b>=</b>) at the beginning of each of your search values to specify how the comparison should be done.
</p>
{{ C.Html.link('Advanced Search', '#', {'class': 'search-button'}) }}
<div class="search-form" style="display:none">
{% include 'views/hoge/_search.twig' with {'model': model} %}
</div><!-- /.search-form -->
{% do this.widget('zii.widgets.grid.CGridView', {
'id': 'hoge-grid',
'dataProvider': model.search,
'filter': model,
'columns': [
'id', 'userid', 'fuga', 'piyo',
{'class': 'CButtonColumn'},
],
}) %}
view raw admin.twig hosted with ❤ by GitHub
{% do this.setBreadcrumbs({
'Hoges': ['index'],
0: model.id,
}) %}
{% do this.setMenu([
{'label': 'List Hoge', 'url': ['index']},
{'label': 'Create Hoge', 'url': ['create']},
{'label': 'Update Hoge', 'url': {0: 'update', 'id': model.id}},
{'label': 'Delete Hoge', 'url': '#', 'linkOptions': {'submit': {0: 'delete', 'id': model.id}, 'confirm': 'Are you sure you want to delete this item?'}},
{'label': 'Manage Hoge', 'url': ['admin']},
]) %}
<h1>View Hoge #{{ model.id }}</h1>
{% do this.widget('zii.widgets.CDetailView', {
'data': model,
'attributes': [
'id', 'userid', 'fuga', 'piyo', 'is_deleted',
]
}) %}
view raw view.twig hosted with ❤ by GitHub
<div class="form">
{% set form = this.beginWidget('CActiveForm', {
'id': 'hoge-form',
'enableAjaxValidation': false,
}) %}
<p class="note">Fields with <span class="required">*</span> are required.</p>
{{ form.errorSummary(model) }}
<div class="row">
{{ form.labelEx(model, 'userid') }}
{{ form.textField(model, 'userid') }}
{{ form.error(model, 'userid') }}
</div><!-- /.row -->
<div class="row">
{{ form.labelEx(model, 'fuga') }}
{{ form.textField(model, 'fuga', {'size': 60, 'maxlength': 64}) }}
{{ form.error(model, 'fuga') }}
</div><!-- /.row -->
<div class="row">
{{ form.labelEx(model, 'piyo') }}
{{ form.textField(model, 'piyo', {'size': 60, 'maxlength': 64}) }}
{{ form.error(model, 'piyo') }}
</div><!-- /.row -->
<div class="row buttons">
{{ C.Html.submitButton(model.isNewRecord ? 'Create' : 'Save') }}
</div><!-- /.row buttons -->
{% do this.endWidget %}
</div><!-- form -->
view raw _form.twig hosted with ❤ by GitHub

わからなかった箇所
  • オプションで 'autoescape' => true とするとすべての HTML タグが変換されてしまう ( ので、手動エスケープで対処 )
  • ビューでオブジェクトのプロパティを設定できない (のでコントローラに書いている )
  • 「CActiveForm ウィジェットの値を $form に代入する」ということがビューではできない (ので CHtml::form() で代用)
  • CHtml::link() を使うと URLに 0%5Bid%5D みたいなものが付いてしまう (ので$this->createUrl で代用)
  • layouts/column2.twig の CPortlet ウィジェットがどうしてもエラーになる (ので省略)
  • Yii::app() は App. で呼び出せるが Yii::getVersion() の呼び出し方がわからない

便利そうだなと思う箇所
  • オプションで CHtml::link()を link() で呼び出せるように設定できる

とりあえず、現段階では Yii の機能も Twig の機能も殺してしまっている感じなので、もう少し勉強したあとで、また再度試してみたいと思います。


追加補足
CHtml::link() で 0%5Bid%5D が付いてしまう件は記述間違いでした。正しくは以下。
echo C.Html.link(data.id|e, {0: 'id', 'id': data.id}); ( 0 を付ける形になります)

Yii::getVersion() などの呼び出し方はコメントで答えを頂きました。参考にしてください。

CActiveForm ウィジェットの件は こちら こちらで解決できました。コメント側で表現されているやり方のほうが良さそうに見えました。

layouts/column2.twig の CPortlet ウィジェットの件も、同じような形で表現して解決できました。column2.html のコードを更新しましたので、参考にしてみてください。

ビューでオブジェクトのプロパティを設定できない件は Controller.php で public setBreadcrumbs($value) { $this->breadcrumbs = $value; } とすることで解決しました (menuプロパティも同様) 。

参考リンク

3 件のコメント:

  1. ETwigViewRenderer の forum に書き込まれていたのでこちらに記事が書かれているかもと思いやってきました。
    protected/config/main.php の globals に

    'viewRenderer'=>array(
    'class'=>'ext.etwigviewrenderer.ETwigViewRenderer',
    'globals' => array(
    'Yii' => 'Yii',
    ),
    ),

    Yii を加えて {{ Yii.getVersion }} で呼び出せましたよ。

    返信削除
    返信
    1. なるほど!ありがとうございます!
      globalsとfunctionsはすごく使えそうだなと思いました
      ただ、多く設定した場合のパフォーマンスが気になりますが

      削除
  2. もひとつ。
    {% set form = this.createWidget() %} すればwidgetをあきらめなくてもいいかも知れません。

    返信削除