(これはHTML v2のドキュメントです。以前のTwig v1を使用するHTML v1も利用可能です。)

HTML

インストール

HTML表示のためにcomposerでTwig v2のモジュールをインストールします。

composer require madapaja/twig-module ^2.0

次にhtmlコンテキストファイルsrc/Module/HtmlModule.phpを用意してTwigModuleをインストールします。

namespace MyVendor\MyPackage\Module;

use Madapaja\TwigModule\TwigErrorPageModule;
use Madapaja\TwigModule\TwigModule;
use Ray\Di\AbstractModule;

class HtmlModule extends AbstractModule
{
    protected function configure()
    {
        $this->install(new TwigModule);
        $this->install(new TwigErrorPageModule);
    }
}

TwigErrorPageModuleはエラー表示をHTMLで行うオプションです。HtmlModuleでインストールしないでProdModuleでインストールして開発時のエラー表示はJSONにすることも出来ます。

次にtemplatesフォルダをコピーします

cp -r vendor/madapaja/twig-module/var/templates var/templates

bin/page.phppublic/index.phpのコンテキストを変更してhtmlを有効にします。

$context = 'cli-html-app'; // 'htm-app'

テンプレート

1つのリソースクラスに1つのテンプレートファイルがvar/templatesフォルダに必要です。 例えばsrc/Page/Index.phpにはvar/templates/Page/Index.html.twig が必要です。

テンプレートにリソースの bodyがアサインされます。

例)

src/Page/Index.php

class Index extend ResourceObject
{
    public $body = [
        ['greeting' => 'Hello BEAR.Sunday']
    ];
}

src/Page/Index.twig.php または var/templates/Page/Index.twig.php

<h1>{{ greeting }}</h1>

出力

php bin/page.php get /
200 OK
content-type: text/html; charset=utf-8

<h1>Hello BEAR.Sunday</h1>

テンプレートファイルの選択

どのテンプレートを使用するかはリソースでは選択しません。リソースの状態によってincludeします。

{% if user.is_login %}
    {{ include('member.html.twig') }}
{% else %}
    {{ include('guest.html.twig') }}
{% endif %}

リソースクラスはリソース状態だけに関心を持ち、テンプレートだけがリソース表現に関心を持ちます。 このような設計原則を関心の分離(SoC)といいます。

エラーページ

var/templates/error.html.twigを編集します。エラーページには以下の値がアサインされています。

変数 意味 キー
status HTTP ステータス code, message
e 例外 code, message, class
logref ログID n/a

{% extends 'layout/base.html.twig' %}
{% block title %}{{ status.code }} {{ status.message }}{% endblock %}
{% block content %}
    <h1>{{ status.code }} {{ status.message }}</h1>
    {% if status.code == 404 %}
        <p>The requested URL was not found on this server.</p>
    {% else %}
        <p>The server is temporarily unable to service your request.</p>
        <p>refference number: {{ logref }}</p>
    {% endif %}
{% endblock %}

リソースのアサイン

リソースクラスのプロパティを参照するにはリソース全体がアサインされる_roを参照します。

例)

Todos.php

class Todos extend ResourceObject
{
    public $code = 200;

    public $text = [
        'name' => 'BEAR';
    ];    

    public $body = [
        ['title' => 'run']
    ];
}

Todos.html.twig

{{ _ro.code }} // 200
{{ _ro.text.name }} // 'BEAR'
{% for todo in _ro.body %}
  {{ todo.title }} // 'run'
{% endfor %}

ビューの階層構造

リソースクラス単位でビューを持つ事ができます。構造を良く表し、キャッシュもリソース単位で行われるので効率的です。

例)app://self/todosを読み込むpage://self/index

app://self/todos

class Todos extends ResourceObject
{
    use AuraSqlInject;
    use QueryLocatorInject;

    public function onGet() : ResourceObject
    {
        $this->body = $this->pdo->fetchAll($this->query['todos_list'])
        return $this;
    }
}
{% for todo in _ro.body %}
  {{ todo.title }}</td>
{% endfor %}

page://self/index

class Index extends ResourceObject
{
    /**
     * @Embed(rel="todos", src="app://self/todos")
     */
    public function onGet() : ResourceObject
    {
        return $this;
    }
}
{% extends 'layout/base.html.twig' %}
{% block content %}
  {{ todos|raw }}
{% endblock %}

拡張

TwigをaddExtension()メソッドで拡張する場合には、拡張を行うTwigのProviderクラスを用意しTwig_EnvironmentクラスにProvider束縛します。

use Ray\Di\Di\Named;
use Ray\Di\ProviderInterface;

class MyTwigProvider implements ProviderInterface
{
    private $twig;

    /**
     * @Named("original")
     */
    public function __construct(\Twig_Environment $twig)
    {
        // $twig is an original \Twig_Environment instance
        $this->twig = $twig;
    }

    public function get()
    {
        // Extending Twig
        $this->twig->addExtension(new MyTwigExtension());

        return $this->twig;
    }
}
class HtmlModule extends AbstractModule
{
    protected function configure()
    {
        $this->install(new TwigModule);
        $this->bind(\Twig_Environment::class)->toProvider(MyTwigProvider::class)->in(Scope::SINGLETON);
    }
}

モバイル

モバイルサイト専用のテンプレートを使用するためにはMobileTwigModuleを加えてインストールします。

class HtmlModule extends AbstractModule
{
    protected function configure()
    {
        $this->install(new TwigModule);
        $this->install(new MobileTwigModule);
    }
}

index.html.twigの代わりにIndex.mobile.twig存在すれば優先して使用されます。変更の必要なテンプレートだけを用意する事ができます。

カスタム設定

コンテンキストに応じてオプション等を設定したり、テンプレートのパスを追加する場合は@TwigPaths@TwigOptionsに設定値を束縛します。

注)キャッシュを常にvar/tmpフォルダに生成するので特にプロダクション用の設定などは特に必要ありません。

namespace MyVendor\MyPackage\Module;

use BEAR\Package\AbstractAppModule;
use Madapaja\TwigModule\Annotation\TwigDebug;
use Madapaja\TwigModule\Annotation\TwigOptions;
use Madapaja\TwigModule\Annotation\TwigPaths;
use Madapaja\TwigModule\TwigModule;
use Ray\Di\AbstractModule;

class AppModule extends AbstractAppModule
{
    protected function configure()
    {
        // ...
        $this->install(new TwigModule);

        // テンプレートパスの指定
        $appDir = $this->appMeta->appDir;
        $paths = [
            $appDir . '/src/Resource',
            $appDir . '/var/templates'
        ];
        $this->bind()->annotatedWith(TwigPaths::class)->toInstance($paths);

        // オプション
        // @see http://twig.sensiolabs.org/doc/api.html#environment-options
        $options = [
            'debug' => false,
            'cache' => $appDir . '/tmp'
        ];
        $this->bind()->annotatedWith(TwigOptions::class)->toInstance($options);
        
        // debugオプションのみを指定する場合
        $this->bind()->annotatedWith(TwigDebug::class)->toInstance(true);
    }
}