HTML (Twig v2)

インストール

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'; // 'html-app'

テンプレート

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

例)src/Page/Index.php:

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

var/templates/Page/Index.twig.php:

<h1></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>reference number: {{ logref }}</p>
    {% endif %}
{% endblock %}

リソースのアサイン

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

例)Todos.php:

class Todos extends 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(): static
    {
        $this->body = $this->pdo->fetchAll($this->query['todos_list']);
        return $this;
    }
}
{% for todo in _ro.body %}
    {{ todo.title }}
{% endfor %}

page://self/index

class Index extends ResourceObject
{
    /**
     * @Embed(rel="todos", src="app://self/todos")
     */
    public function onGet(): static
    {
        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 は元の \Twig_Environment インスタンス
        $this->twig = $twig;
    }

    public function get()
    {
        // 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);
    }
}