Apricotでは、テンプレートエンジンにLaravelと同じBladeを使用しています。実際に使用しているライブラリは BladeOne です。BladeOneは、Blade のスタンドアロンバージョンです。
テンプレートの詳しい使用方法については、以下のドキュメントを参照して下さい。
Apricotでは、BladeOneを直接使う代わりに、それをラップしてシングルトンにしたViewクラスを使用します。ViewシングルトンはBladeOneと同じメソッド使用できますが、Apricotで主に使用するのはrun()メソッドだけです。
使用法: View::{メソッド}
メソッド | 機能 |
---|---|
string run(string $view, array $variables = []) | テンプレートエンジンの実行 |
Apricotのアプリケーションでは、直接Viewシングルトンを使用する代わりに、ボイラープレートrender()
を使って、HTMLをレンダリングします( 即ち、render()の内部でView::run()が使用されています )。以下はユーザコントローラのindexアクションの例です。
public function index() { $users = $this->user->findAll(); return render("user.index", ["users"=>$users]); }
render() の第1引数にはHTMLテンプレート名、第2引数にはテンプレート変数を[変数名 ⇒ 値]の形で渡します。
テンプレートファイルは、/your-project/assets/view/
に配置します。一般的にテンプレートファイルは、/your-project/assets/view/{path}/{name}.blade.php
として保存します。{path}
はサブディレクトリ下に配置したい場合のオプションで、コントローラ名に関連した名前を付けるのが一般的でしょう。{name}
はテンプレート名です。上例のテンプレートファイルは、/your-project/assets/view/user/index.blade.php
に配置し、テンプレート名は user.index
になります。
以下は、テンプレートファイル index.blade.php
からの抜粋です。render()メソッドで与えらてたテンプレート変数 $users を使って各要素( $user )を取り出し、ユーザデータをレンダリングしています。
@foreach($users as $user) <tr data-href="{{route("user/{$user->id}/edit")}}"> <td>{{ $user->id }}</td> <td>{{ $user->account }}</td> <td>{{ $user->email }}</td> <td>{{ ViewHelper::formatDatetime($user->created_at) }}</td> </tr> @endforeach
render() 関数は、Apricotコア部分下の helpser/boilerplates.php
の中で以下のように実装されています。
/** * Renders HTML via the given template and returns a response object. * * @param string $view Template name * @param array $variables An array of template variables * @return \Apricot\Foundation\Response\RenderResponse */ function render(string $view=null, array $variables=[]):Apricot\Foundation\Response\RenderResponse { $variables['errors'] = errors(); $html = isset($view) ? Apricot\View::run($view, $variables) : null; return new Apricot\Foundation\Response\RenderResponse($html); }
render() 関数の2つの引数は View シングルトンのrun()メソッドに渡わたされ、テンプレートを介してHTMLをレンダリングし、その結果を使ってレンダーレスポンスを生成して返しています。ボイラープレート errors() については次項を参照して下さい。
$errors
は render()
ボイラープレートによって自動的に作られるテンプレート変数です。$errors
は errors()
ボイラープレートによって取得されます。
// テンプレート変数 $errors ['errors' => errors()]
errors()
ボイラープレートはフラッシュに保存されているエラーバッグを返します(このフラッシュデータの名前は 'errors' です)。$errors
はテンプレートの中で次のようにして使用されます:
@if($errors->count()) @foreach($errors as $key=>$value) {{$value}}<br> @endforeach @endif
Aprocotのアプリ部分では、ヘルパークラスを /your-project/app/Helpers
に配置しています。
テンプレート内で使用するヘルパークラスはビューヘルパーと呼ばれ、以下は、初期実装されている ViewHelperクラス で、文字列の日付をフォーマットするメソッドだけが実装されています。必要に応じて、ここにメソッドを追加して下さい。
/your-project/app/Helpers
<?php namespace App\Helpers; /** * View Helper */ class ViewHelper { /** * Returns a formatted date string. * * This method is an example of a view helper. * * @param string $datetime * @param string $format * @return string */ static function formatDatetime(string $datetime, string $format='Y-m-d'):string { return date($format, strtotime($datetime)); } }
Apricotでは、シングルトンとビューヘルパーのエイリアスを作っています。これによって、完全修飾クラス名を短くコーディングできます。クラスエイリアスの設定については、「Apricot 配置と構成」を参照して下さい。
以下はテンプレート内でのクラスエイリアスの使用例です。この例では、フラッシュにキーがmsgのデータがあればそれを出力しています。
@if(Flash::has('msg')) <div class="message">{{Flash::get('msg')}}</div> @endif
テンプレートファイルは、/your-project/assets/views/
の下に配置し、ファイル名は .blade.php
で終わる必要があります。例えば、/your-project/assets/views/{path}/{name}.blade.php
として保存した場合、テンプレート名は、{path}.{name}
になります。
Apricotのアプリ部分として提供しているテンプレートファイルの構成を以下に示します。
your-project | ├── assets | ├── views | | | | | ├── error [エラーページ用] | | | | | | | ├── layout.blade.php [エラーページ用のマスターレイアウト] | | | └── exception.blade.php [集約例外コントローラ用] | | | | | ├── user [ユーザコントローラ用] | | | | | | | ├── create.blade.php [ユーザ新規登録ページ] | | | ├── edit.blade.php [ユーザ編集ページ] | | | └── index.blade.php [ユーザリストページ] | | | | | ├── home.blade.php [ホームコントローラ用] | | ├── layout.blade.php [通常ページ用のマスターレイアウト] | | ├── login.blade.php [ログイン認証コントローラ用] | | └── stub.blade.php [スタブコントローラ用]
1つのコントローラーが複数のHTMLテンプレートを持っている場合、ユーザコントローラのように、サブディレクトリ―にはコントローラ名に関連した名前付けを推奨します。上のユーザ新規登録ページのテンプレート名は user.create
です。
layout.blade.php
ログイン認証とエラーページ以外のマスターレイアウトとして使用されます(次項参照)。
通常のアプリケーションでは、ページの全体的なレイアウトの中に個別のレイアウトを表示します。この全体的なレイアウトをマスターレイアウトと呼びます。以下は、マスターレイアウトのテンプレート ( layout.blade.php ) の例です。
/your-project/assets/views
{{-- This is an example of master layout --}} <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{__('messages.app.title')}}</title> <!-- Here are stylesheets and scripts such as jquery and bootstrap --> {!! DebugBar::renderHead() !!} @stack('scripts') </head> <body> <nav> <!-- Here is the navigation menu bar--> </nav> <main> <h1>@yield('title')</h1> @yield('content') {{-- error --}} @if($errors->count()) <div class="alert"> @foreach($errors as $key=>$value) {{$value}}<br> @endforeach </div> @endif </main> <footer>{{env('APP_NAME')}} © 2020 y2sunlight</footer> {!! DebugBar::render() !!} </body> </html>
マスターレイアウトの例に従ってBladeによるテンプレートの書き方を確認してみましょう。
Bladeのコメントは {{-- --}}
表記を使います。このコメント表記はHTMLを出力しません。
{{-- This is an example of master layout --}}
Bladeからデータを表示するには、{{ }}
表記を使います。これは自動的にhtmlspecialchars()を呼び出して、HTMLをエスケープしXSS攻撃を防ぎます。以下は、ヘルパー関数 __()
による翻訳文字列とテンプレート変数の表示例です。
<title>{{__('messages.app.title')}}</title> {{$value}}<br>
HTMLをエスケープしたくない場合は以下のように、{!! !!}
表記を使います。これは デバッグ出力 用のヘッダーと本体部分をレンダリングしている例です。
{!! DebugBar::renderHead() !!} {!! DebugBar::render() !!}
Bladeでは、@で始まるディレクティブと呼ばれる構文があります。@yield
と @section
ディレクティブは対で使用されます。親テンプレートの @yield で名前付きのセクションを宣言し、子テンプレートの@sectionでそこに表示するHTMLを定義します。以下の例では、マスターレイアウトでのタイトルとコンテンツセクションを宣言しています。@sectionの例は、次項を参照して下さい。
@yield('title') @yield('content')
@stack
と @push
ディレクティブは、テンプレートにスタック構造を与えます。親テンプレートの @stackで名前付きのスタックを宣言し、子テンプレートの@pushでそこに表示するHTMLをプッシュします。@yield
- @section
との違いは、必要に応じてHTMLを何度もプッシュできます。以下の例では、マスターレイアウトのHEAD要素の中にスクリプトのスタックを宣言しています。@pushの例は、次項を参照して下さい。
@stack('scripts')
PHPの制御構造と同様に、Blade でも @if
、@switch
、@for
、@foreach
、そして @while
の制御構文が使用できます。以下は、@if と @foreach の例です。これはエラー表示用のテンプレート変数からエラー情報を表示している例です。
@if($errors->count()) <div class="alert"> @foreach($errors as $key=>$value) {{$value}}<br> @endforeach </div> @endif
@phpディレクティブを使えば、任意のPHPコードを実行できます。
@php /* Do something. */ @endphp
前項のマスターテンプレートを継承した例を以下に示します。これはユーザコントローラーのcreateアクションで、ユーザの新規登録を行うページです。
/your-project/assets/views/user
{{-- Parent layout --}} @extends('layout') {{-- Additional script --}} @push('scripts') <script src="{{url_ver('js/user.js')}}"></script> @endpush {{-- title --}} @section('title', __('messages.user.create.title')) {{--content --}} @section('content') <form method="POST" name="fm"> @csrf {{-- account --}} <div> <label for="account">{{__('messages.user.create.account')}}</label> <input type="text" name="account" id="account" value="{{old('account',$user->account)}}" placeholder="{{__('messages.user.create.hint_account')}}"> </div> {{-- Show other input elements here --}} </div> </form> @endsection
上の例に従ってBladeによるテンプレートの書き方を確認してみましょう。
マスターテンプレートを継承するには、@extendsディレクティブを使用します。このディレクティブを使えばマスターテンプレートの中に個別のコンテンツを表示することができます。
@extends('layout')
マスターテンプレートで宣言されている@stackに個別のコンテンツをプッシュするには、@pushディレクティブを使います。下の例では、マスターテンプレートのHEAD要素内に個別のJavascriptを差し込んでいます。
@push('scripts') <script src="{{url_ver('js/user.js')}}"></script> @endpush
マスターテンプレートで宣言されている@yieldに個別のコンテンツを表示するには、@sectionディレクティブを使います。以下は個別のタイトルを表示する例です。
@section('title', __('messages.user.create.title'))
また、複数行のセクションを定義するには以下の構文を使います。
@section('content') {!-- Display the form element here. --} @endsection
@csrfは、CSRF対策用のトークンフィールドを出力するカスタムディレクティブです。@csrfの作り方については「Bladeのセットアップ」を参照して下さい。
<form method="POST" name="fm"> @csrf {!-- Display input elements here. --} </form>
Bladeの設定ファイルは、bladeone.setting.php
です。
your-project/config/setting
<?php /** * This file contains BladeOne settings. */ return [ 'template_path' => env('VIEW_TEMPLATE',assets_dir('views')), 'compile_path' => env('VIEW_CACHE',var_dir('cache/views')), 'mode' => \eftec\bladeone\BladeOne::MODE_AUTO, ];
実行モードが MODE_AUTO の場合、テンプレートはPHPへコンパイルされ、変更があるまで compile_path
で指定された場所にキャッシュされ続けます。強制的に再コンパイルしたい場合は、キャッシュをクリアして下さい。
実行モードには以下の種類があるます:
Bladeには以下のセットアップファイルが存在します。
/your-project/config/setup
<?php /** * Initial setting of View template (Blade One) */ return function():bool { // @now directive Apricot\View::directive('now', function() { return "<?php echo date('Y-m-d H:i'); ?>"; }); // @csrf directive Apricot\View::directive('csrf', function() { $name = Apricot\Foundation\Security\CsrfToken::CSRF_KEY; return '<input name="'.$name.'" type="hidden" value="{{Session(\''.$name.'\')}}">'; }); return true; // Must return true on success };
このセットアップファイルの目的は、HTMLテンプレートに新しいカスタムディレクティブを追加することです。ここでは以下のディレクティブを追加しています。
現在時刻を表示するディレクティブです。これはカスタムディレクティブのサンプルです。
CSRF対策用のトークンフィールドを出力するディレクティブです。セッションからCSRFトークンを取得して、hidden タイプの input 要素の中に設定しています。
Apricotで使用しているテンプレートエンジン BladeOne には、オプションの拡張ライブラリーとして BladeOneHtml
があります。このライブラリーを使用すると、HTMLフォームが簡単に綺麗に作成できます。
例:
@form() @input(type="text" name="myform" value=$myvalue) @button(type="submit" value="Send") @endform()
インストール方法や使い方は、以下を参照して下さい:
Apricotは多言語をサポートしてます。これはアプリケーションで各言語毎にメッセージをファイルを作成することによって行い、このファイルの事を言語ファイルと呼びます。
アプリケーションの実行時に、Apricotがどの言語を選択するかは、利用者がブラウザで使用している言語とアプリケーションで準備されている言語ファイルによって自動的に決定されます。デフォルトの言語は、環境変数 APP_LANG で指定してます。この変数が指定されていない場合は英語になります。
言語ファイルは /your-project/assets/lang/{言語コード}/
に配置されます。言語コードは、ISO 639-1で定義された2文字のコードです。言語ファイル名は各言語共通で、内容を同じキーで定義しなければなりません。Apricotの初期実装では以下のディレクトリーと言語ファイルが用意されています。
your-project [プロジェクトディレクトリー] | ├── assets | | | ├── lang/ | | | | | ├── en [英語] | | | ├── auth.php [ログイン認証画面用] | | | ├── messages.php [一般的なメッセージ] | | | └── vlucas.valitron.php [バリデーション用] | | | | | ├── ja [日本語] | | | ├── auth.php | | | ├── messages.php | | | └── vlucas.valitron.php
メッセージの識別は、ドット表記のキーで行います。最初のキーは、言語ファイルの .php
を除いたベース名です。キーの残りの部分は、それに続いてドットで区切られた階層が続きます。
以下は、言語ファイル(messages.php)の例です。このファイル内のメッセージを取得するドット表記のキーは messages.home.title
と messages.home.msg_hello
です。後者のメッセージには :account
という名前の付いた1つのパラメータを含んでいます。
/your-project/assets/lang/en
<?php return [ 'home'=>[ 'title'=>env('APP_NAME'), 'msg_hello'=>'Hello, :account !', ], ];
Langクラスはドット表記で指定されたキーから各言語用のメッセージを取得するシングルトンです。このクラスは Apricot\Foundation\Translation
クラスをシングルトンにしたものです。
使用法: Lang::{メソッド}
メソッド | 機能 |
---|---|
string getLangCode() | 言語コード(ISO 639-1)の取得 |
bool has(string $key) | キーの存在確認 |
string get(string $key, array $params = []) | 言語テキストの取得 |
以下は、ホームコントローラのindexアクションです。この例では、前項の例の言語ファイル( messages.php )から 'messages.home.msg_hello' のキーを持つメッセージを取得した後、それをテンプレートに渡しHTMLをレンダリングしています。
public function index() { $message = Lang::get('messages.home.msg_hello', [':account'=>AuthUser::getUser()->account]); return render('home',['message'=>$message]); }
Lang::get()
メソッドを使用する代わりに __()
ボイラープレートを使って以下のようにもコーディングできます。
public function index() { $message = __('messages.home.msg_hello', [':account'=>AuthUser::getUser()->account]); return render('home',['message'=>$message]); }
この関数名は __ です。2つ並んだアンダースコアはPythonプログラマーの間ではdunders
(double underscoreの意) と呼ばれ特別なクラス内メンバに付加されますが、ここではそのような意味はなくLaravelと同じ関数名にしました。
通常のブラウザでは、静的なアセットファイル( .js
, .css
または画像ファイル)がキャッシュされる場合があります。これは、アプリケーションのアセットファイルを変更した場合に問題が発生するかもしれません。これを防ぐ手段をキャッシュ・バスティング( Cache Busting
)と呼びます。
簡単なキャッシュ・バスティングの方法としては、アセットファイルのURLに現在日時をクエリー文字列として付加する方法があります。
<link href="{{url('css/main.css').'?'.time()}}" rel="stylesheet"> <script src="{{url('js/main.js').'?'.time()}}"></script>
しかし、この方法では、アクセスする度にブラウザがアセットファイルを取得してしまい、ブラウザのキャッシュ機構が上手く機能しません。従って、現在日時の代わりにアプリケーションのバージョン番号を付加すれば、この問題を解決することができます。これがアセットファイルのバージョニングです。
Apricotではバージョニング用のボイラープレート url_ver() 関数が用意さており、上のコードを以下のように変更することで、アセットファイルのバージョニングができます。
<link href="{{url_ver('css/main.css')}}" rel="stylesheet"> <script src="{{url_ver('js/main.js')}}"></script>
アプリケーションのバージョン番号は、環境変数 APP_VERSION
から取得します。従って、アセットファイルのバージョニングを使用している場合は、アセットファイルを変更したら必ず APP_VERSION
も変更する必要があります。