【PHP/Slim4】３．軽量フレームワーク(Slim4-skeleton) Twig

## やりたいこと
Slim4のテンプレートエンジンを使ってみる
Twigがメジャーっぽいので、なんとなくTwigを使ってみようと思う

## Step1．サンプル環境の構築
[前回の記事を参照](https://qiita.com/miyaken3381/items/a63f70b65a01c8b77225)

## Step2．Twigのインストール
プロジェクトフォルダに移動後、以下のコマンドを実行
~~~js
composer require slim/twig-view
~~~

## Step3．Twigの依存関係を設定
dependencies.phpにTwigの依存関係を注入する
またsettings.phpでパス等の設定を切り替えられるようにしておく
#### app/settings.php
~~~php
return function (ContainerBuilder $containerBuilder) {

    // Global Settings Object
    $containerBuilder->addDefinitions([
        SettingsInterface::class => function () {
            return new Settings([
                ：（略）
                ],
                // Twig設定
                ,'twig' => [
                     'reloadCache'      => time()
                    ,'strict_variables' => true
                    ,'cache'            => __DIR__ . '/../var/cache/twig'
                ]
                // CSS,JS,画像置き場
                ,'assets' =>[
                    'path'              => '/assets'
                ]
            ]);
        }
    ]);
~~~

#### app/middleware.php
~~~php
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;

return function (App $app) {
    $app->add(SessionMiddleware::class);
	$app->add(TwigMiddleware::createFromContainer($app, Twig::class));
};

~~~

#### app/dependencies.php
~~~php
use Slim\Views\Twig;

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([
        LoggerInterface::class => function (ContainerInterface $c) {
                ：（略）
        },
        // Twig
        Twig::class => function (ContainerInterface $container) {
            // Twig設定
            $settings = $container->get(SettingsInterface::class);
            // テンプレートの置き場所設定
            $renderTwig = Twig::create(__DIR__ . '/../templates', $settings->get('twig'));
            // Css/Jsのキャッシュ読み込み (hoge.css?v=xxxx 対応)
            $renderTwig->offsetSet('reloadCache', $settings->get('twig')['reloadCache']); 
            // CSS/JSなどのファイル格納
            $renderTwig->offsetSet('assetsUrl', $settings->get('assets')['path']);
            return $renderTwig;
        }
    ]);
~~~

#### 解説
1. settings.php：twig->reloadCacheは独自定義。CSSファイルやJSファイルのキャッシュ対応。開発時にキャッシュが効きすぎてCSS/JSの更新が行えないのがだるいので、現時刻のUnix時刻を設定している。後述のテンプレートで使用する
1. settings.php：twig->strict_variablesはTwigの定義。false：Twigのエラーを無視。true：Twigのエラーを検知なので、基本はtrueを設定が良い
1. settings.php：twig->cacheはTwigの定義。Twigでレンダリングしたビューのファイルキャッシュ置き場を指定する
1. settings.php：assets->pathは独自定義。CSSやJavaScriptファイルの置き場を指定。後述にテンプレートで使用
1. middleware.php: Twigのミドルウェア追加
1. dependencies.php：settingsは必ず設定。固定形式
1. dependencies.php：renderTwigは必ず設定。Twigテンプレートファイルの置き場所を指定
1. dependencies.php：renderTwig->offsetSetは独自定義。offsetSetメソッドで独自の設定値をTwigに渡すことが可能。後述のテンプレートで使用

## Step4．Twigのテンプレートを作成する
例として以下のテンプレート構成で作成を行う
|ファイル名|概要|
|---|---|
|templates/layoutBase.html.twig|親のテンプレート|
|templates/childHoge.html.twig|子テンプレート その１|
|templates/childFuga.html.twig|子テンプレート その２|

#### templates/layoutBase.html.twig
~~~php
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="utf-8">
	<link rel="icon" href="data:,">
</head>
<body>
    {# Twigコメント、共通項目 #}
	<div>
		{# Twig変数読み込み #}
		これは{{ templateTitle }}です

		{# Twigの条件判定 #}
		{% if isAddComment is defined and isAddComment == true %}
			{% block AddComment %}{% endblock %}
		{% endif %}
	</div>

    {# Twigコメント、子テンプレート #}
	<div>
		{% block childTemplate %}{% endblock %}
	</div>

	<!-- 個別JavaScript -->
	{% block includeJs %}{% endblock %}
</body>
</html>
~~~

#### templates/childHoge.html.twig
~~~php
{# 基本レイアウト #}
{% extends 'layoutBase.html.twig' %}


{# JSファイル 設定 #}
{% block includeJs %}
	<script type="text/javascript" src="../{{ assetsUrl }}/js/hoge.js?v={{ reloadCache }}"></script>
{% endblock %}


{# メインメニュー 設定 #}
{% block childTemplate %}
	hogeファイルを読み込んだ
{% endblock %}
~~~

#### templates/childFuga.html.twig
~~~php
{# 基本レイアウト #}
{% extends '../templates/layoutBase.html.twig' %}

{# JSファイル 設定 #}
{% block includeJs %}
	<script type="text/javascript"></script>
{% endblock %}

{% set isAddComment = true %}
{% block AddComment %}
	fugaの追加コメントです
{% endblock %}


{# メインメニュー 設定 #}
{% block childTemplate %}
	fugaファイルを読み込んだ
{% endblock %}
~~~

#### 解説
1. layoutBase.html.twig：親テンプレートファイル。共通の画面レイアウトや共通のJavaScriptなどを設定する。画面ごとに分かれる内容を{% block xxxxxx %}{% endblock %}で、子テンプレートから取り込む
1. childHoge.html.twig/childFuga.html.twig：extendsで親テンプレートを指定する
1. childHoge.html.twig/childFuga.html.twig：{% block xxxxxxx %}で、子テンプレートごとの内容を記載。親テンプレートに読み込ませる。
1. childFuga.html.twig：{% set xxxxxxx = true %}で、子テンプレートごとに変数定義が可能。親テンプレートで読み込ませ表示の出しわけが可能

## Step5．Twigのテンプレートを読み込ませるイベントを作成
以下の設定を行い、テンプレートの読み込ませるActionイベントを作成する
|ファイル名|概要|
|---|---|
|app/routes.php|URL/Actionイベントの設定|
|src/Application/Actions/Test/HogeAction.php|子テンプレート その１のAction|
|src/Application/Actions/Test/FugaAction.php|子テンプレート その２のAction|

#### app/routes.php
~~~php
use Slim\Interfaces\RouteCollectorProxyInterface as Group;
use App\Application\Actions\Test\HogeAction;
use App\Application\Actions\Test\FugaAction;

return function (App $app) {
    ：（略）   
    $app->group('/twig', function (Group $group) {
        $group->get('/hoge', HogeAction::class);
        $group->get('/fuga', FugaAction::class);
    });
};

~~~

#### src/Application/Actions/Test/HogeAction.php
~~~php
<?php

declare(strict_types=1);

namespace App\Application\Actions\Test;

use App\Application\Actions\Action;
use Slim\Views\Twig;
use Psr\Log\LoggerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use App\Application\Settings\SettingsInterface;

class HogeAction extends Action{
	private $twig;

	public function __construct(LoggerInterface $logger, Twig $twig, SettingsInterface $settings) {
		parent::__construct($logger, $twig, $settings);
		$this->twig = $twig;
	}

	/**
	 * {@inheritdoc}
	 */
	protected function action(): Response {
		$template  = './childHoge.html.twig';
		$title     = 'Hoge';

		return $this->twig->render($this->response, $template, 
			[ 'templateTitle' => $title]);
	}
}
~~~

#### src/Application/Actions/Test/FugaAction.php
~~~php
<?php

declare(strict_types=1);

namespace App\Application\Actions\Test;

use App\Application\Actions\Action;
use Slim\Views\Twig;
use Psr\Log\LoggerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use App\Application\Settings\SettingsInterface;

class FugaAction extends Action{
	private $twig;

	public function __construct(LoggerInterface $logger, Twig $twig, SettingsInterface $settings) {
		parent::__construct($logger, $twig, $settings);
		$this->twig = $twig;
	}

	/**
	 * {@inheritdoc}
	 */
	protected function action(): Response {
		$template  = './childFuga.html.twig';
		$title     = 'Fuga';

		return $this->twig->render($this->response, $template, 
			[ 'templateTitle' => $title]);
	}
}
~~~

#### 解説
1. routes.php：アクセスするURIの指定。何かしらのAction追加するときには、このファイルは編集対象となる
1. HogeAction.php/FugaAction.php：useで取り込む依存関係。定型文のようなものなのでまねておく
1. HogeAction.php/FugaAction.php：__constructは、Action起動時に一番はじめによばれるメソッド。依存関係の設定をしていれば、TwigのインスタンスなどがSlimから引数で渡される。Twigのレンダリングをするために、Private変数にTwigインスタンスを保存
1. HogeAction.php/FugaAction.php：actionは、View処理メソッド。templete変数にActionに対応したTwigテンプレートの指定。twig->renderでテンプレートに描写を行う。[]に連想配列で値を設定すれば、Twigテンプレートにその値を渡すことが可能（サンプルでは、templateTitle変数のみを引き渡している）

## Step6．Twigの表示確認
routes.phpで設定したURIにアクセスし表示を確認する。
もしこのとき、「Looks like you try to load a template outside configured directories」というエラーが画面に表示された場合、Twigのテンプレートファイルの指定/格納場所が、同一ディレクトリ内にない可能性がある。Twigの制約に引っかかっている可能性がある。
Twigテンプレートの格納位置を調整するか、以下をコメントアウトすることで制約の解除が可能
※Twigのバージョンによってはファイル名が異なる可能性がある
#### vendor/twig/twig/src/Loader/FilesystemLoader.php
~~~php
    private function validateName(string $name): void
    {
       ：（略）
        foreach ($parts as $part) {
            ：（略）
            if ($level < 0) {
//                throw new LoaderError(sprintf('Looks like you try to load a template outside configured directories (%s)(%d).', $name,$level));
            }
        }
    }
~~~

#### 解説
1. FilesystemLoader.php：level変数でTwigテンプレートの階層をチェックしているため、ifの真条件をコメントアウトすることで、階層制限が解除できる。ただし、解除することによる弊害は不明