Laravelのビュー
以前サンプルアプリを作成し@ifや@forなどのLaravelの基本的なビューの書き方を理解しましたが、それでもユーザー認証用にインストールしたLaravel Breezeが挿入したコード群を見ると、なにやら不思議なものがあったりします。そこで、公式ページのビューに関するドキュメントを参考に、その理解を深めます。
ビューのインスタンス生成
ビューのインスタンス生成はヘルパー関数であるviewから行えましたが、他にもにもViewファサードからmakeメソッドを使う事でも同様のことができます。
通常resources.viewsにblade.phpファイルを作成すると、そのビューは先ほどの関数を使って呼び出せるようになります。
しかし、viewsフォルダにサブフォルダがある場合は単に名前を指定しただけでは表示できません。そのような際は、フォルダ名.ビュー名として指定します。
ちなみにこの.(ピリオド)で階層を表すのはルーターの名前などLaravelの他の機能でも用いられたりします。
Route::get('/', function () {
return View::make('test.welcome');
});
viewヘルパ関数やView::make関数を利用する際、第2引数に連想配列をセットしてパラメータを渡すことができます。また連想配列形式の変わりにwithメソッドを使うこともできます。
View::make('sample',['key1'=>value1,'key2'=>value2]);
view('sample')->with('key1','value1')->with('key2','value2');
ビューにおける共通変数
複数のビューにまたがる変数(定数として使うケースが多いかもしれません)を定義する際は、Viewファサードのshareメソッドを使います。
使い方としては「AppServiceProvider」のbootに次のように書いておくと、どこの.blade.phpからでも使える定数として利用できます。
App¥Providers¥AppServiceProvider.php
...
public function boot(){
View::share('C_BORDER_VALUE',100);
}
こうして書いておけば、すべてのビューで次のように取り出せます。
sample.blade.php
...
{{ $C_BORDER_VALUE }}
...
公式サイトではshareメソッドはサービスプロバイダクラスのbootメソッドに記述するものという説明がありましたが、他の場所でセットしたところ変更が反映されました。
また他のコードから値を取り出すときは、sharedメソッドから取得できます。この時、第2引数にキー値が存在しなかった場合の値を設定できます。
View::shared('C_BORDER_VALUE','未存在時の値');
ビューコンポーザー
複数のビューに共通したパラメータを渡したり、共通の処理を加えたいというケースではビューコンポーザーを利用すると便利です。
Composerファイルは、App¥View¥Composersに作成します。ここではTestComposer.phpとしました。
TestComposer.php
<?php
namespace App¥View¥Composers;
use Illuminate¥View¥View;
class TestComposer {
protected $commonValue=1;
public function __construct(){}
public function compose(View $v) {
$v->with('commonvalue',$this->commonValue);
}
}
このコンポーザーをサービスプロバイダーのbootメソッドに登録します。ここではAppServiceProviderに記述します。
testビューに対して、TestComposerをセットしています。複数のビューに対してセットする場合はビュー名を配列として渡します。すべてのビューに適用するならワイルドカード(*)とすることもできます。またこの時、第2引数にはクロージャを指定することもできます。
AppServiceProvider.php
<?php ...
use Illuminate¥View¥View;
public function boot() {
View::composer('test',TestComposer::class);
View::composer('test2',function($view){ ... });
View::composer(['test3','test4'],function($view){ ... });
View::composer('*',function($view){ ... });
}
...
View::composerと同じようなメソッドにView::creatorがあります。composerはViewのレンダリングが終わった後に処理をするのに対し、creatorはビューのインスタンスが生成された直後に実行されます。
このクラスcreatorはApp¥View¥Creatorsに置き、処理を記述するメソッドもcomposeではなくcreateとします。この他はcomposerと同様の記述で利用可能です。
ビューのプリコンパイル
Laravelで作成したビューは、通常はオンデマンドでコンパイルされます。この時間を短縮する為に、あらかじめビューをコンパイルしておくのがプリコンパイルで、次のコマンドで出力されます。
PS> php artisan view:cache
ビューの変更を検知するとLaravelはビューを再コンパイルしますが、生成したこれらのキャッシュをクリアしたい場合はview:clearを実行します。
PS> php artisan view:clear
コンポーネント
ビューでは、コンポーネントをつかうことで、他で使いまわすことのできる小さなパーツ群を作成することができます。このようなものには、たとえばemailを入力するinput要素などといったものがあります。
コンポーネントには、匿名コンポーネントと、クラスベースコンポーネントがありますが、まず匿名コンポーネントについて説明します。
匿名コンポーネントとはbladeテンプレートのみでクラスを含まないコンポーネントのことで、つぎのようにして作成することができます。
PS> php artisan make:component forms.inputEmail --view
viewオプションをつけない場合は、クラスベースコンポーネントが作成され、後述するblade.phpが出力される他、App¥View¥Components¥に指定した名前のphpファイルが出力されます。この時フォルダ階層の区別に.を使うと意図しないファイル名になってしまうので、クラスベースコンポーネントで階層を設定するには「php artisan make:component Forms¥ClassBase」というように通常のセパレータを用います。
php artisanで生成する際はキャメルケースでファイル名を指定しますが、bladeのファイル名はケバブケースになります。
ここでは.(ピリオドの前にforms)という接頭を付けて、サブフォルダ名をformsと指定してますので、コンポーネントはresources¥views¥components¥formsに、input-email.blade.phpという名前で生成されます。フォルダ指定の接頭をつけなければ、componentsのルートに出力されます。このファイルはLaravelに自動認識されるので他の設定が不要で次のように.blade.phpから呼び出して使うことができます。
sample.blade.php
<x-forms.input-email id="email" data-secret="secret" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus />
valueの前の:を付けることで、""で囲まれた中でLaravelの変数やメソッドが使えます。Vue.jsなどで「:属性」という書式を使っていてLaravel側では処理させたくない場合は「::属性」と:を重ねることでエスケープします。
ちなみにvalue="{{...}}"とすることでも可能です。これはHTMLの属性としてコンポーネントに渡す方法でなので、渡せる値は文字列に限られます。:を使えばPHPの配列を渡すことができます(とはいえvalue属性に配列を渡すのはあまりいい方法ではないと思います)。
匿名コンポーネントのファイルの記述の仕方は基本的にbladeの記述と同じですが、その他に次のようなものが使えます。
input-email.blade.php
@props(['disabled' => false])
<input {{ $disabled ? 'disabled' : '' }} {!! $attributes->merge(['class' => 'rounded-md shadow-sm']) !!}>
@propsで指定する連想配列のキーに、呼び出し元のbladeから受け取りたい属性名をキーにして、未存在時のデフォルト値を値にします。そうすることでコード中で属性名の先頭に$を付けた変数で利用できるようになります。ここではdisabled属性をデフォルト値falseで受け取り、後のinputタグでその値を使って要素のdisabled属性の出力有無を分岐させています。
デフォルト値が不要な場合は、キー値のみセットすることも可能です。
また、$attributesには、呼び出し側で記述した属性すべてが入ります(ただし@propsで指定したものは除かれます)。このことを「属性バッグ」とよびます。属性バッグからmergeメソッドを呼ぶことで指定した属性の値を追加しています。これはclass属性とそれ以外で挙動が変わります。class属性に関しては存在すれば統合しなければ追加となりますが、他の属性の場合はデフォルト値扱いです。呼び出し元が存在した場合は呼び出し元の値、そうでなければmergeで指定された値をセットします。
クラス以外のmergeでそのような挙動を望まない場合は、mergeのキーの値をprependsメソッドでラップします。「$attributes->prepends('additional-value')」、そうすることでクラス同様のマージとなります。
もしクラスをmergeしたい場合は、classメソッドもあります。こちらは通常はクラス名となるキーだけの配列になりますが、値にboolean値を付与することでtrueの時のみ対応するクラスをマージすることも可能です。
...
$attributes->class(['classname','error'=>$isError]);
もし{{ }}で囲むことによるエスケープ処理が意図しない挙動になるのなら、{!! !!}に変更します。
クラスベースコンポーネント
匿名ではないコンポーネントは、クラスベースコンポーネントと呼びます。先のコマンドで--viewオプションを付けずに実行するとこちらが生成されます。blade.phpの出力の他、App¥Http¥View¥Componentsフォルダにクラスが生成されます。こちらは作成時のフォルダ階層の区切りを.にするとファイル名がおかしくなるのでForms¥ClassNameというように通常のセパレータを使って生成します。
コンポーネント用の.blade.phpは、このクラス側でviewとして返します。
呼び出し元から受け取りたい属性はコンストラクタで設定します。テンプレートで使用する属性とデータを連携させたい場合は、属性名の先頭に$を付けた変数をコンストラクタに設定することで受け取ることができ、それをパブリックプロパティに保存する等して用います。テンプレート内のパブリックプロパティは、blade.php側からそのままプロパティ名で呼び出すことができます。
また、属性がケバブケース(kebab-case)になっている場合は受け取る変数名はキャメルケース(CamelCase)へ変換します。
input-email.php
<?php
namespace App¥View¥Components;
use Illuminate¥View¥Component;
class InputEmail extends Component{
// public プロパティにすることで
// blade.phpから{{$id}}で呼び出せます。
public $id ='';
public $secret='';
// data-secretは$dataSecretで受け取ることが可能
// (ケバブケースからキャメルケースへ)
public function __construct($id,$datasSecret){
$this->id=$id;
$this->secret=$dataSecret;
}
// コンポーネントを返すための関数(必須)
public function render(){
return view('components.forms.input-email');
}
// blade.phpで利用できる任意のメソッド
public function getValue() {
return "value";
}
// 指定された名前のプロパティやメソッドを
// blade.phpで利用できなくします
protected $except = ['secret'];
}
さらにクラスベースコンポーネントでは匿名コンポーネントとは違ってx-から始まるタグで直接呼び出すことはできません。呼び出すための名前をサービスプロバイダに登録する必要があります。この時登録にはBladeファサードのcomponentメソッドを利用します。
App¥Providers¥AppServiceProvider.php
...
use Illuminate¥Support¥Facades¥Blade;
public function boot(){
Blade::component('input-email',InputEmail::class);
}
上記のように設定した際、blade.php内でコンポーネントはx-input-emailとして利用することができます。
スロット
コンポーネントをタグで呼び出した際、そのままの状態だとコンポーネント用の開始タグと終了タグの間に書いた記述は何も表示されません。
sample.blade.php
...
<x-form.email><h3>スロット</h3></x-form.email>
...
内側に書いたHTMLコードはコンポーネントの$slot変数内に格納されます。この変数をコンポーネント内で使用することで中身を反映させます。
この時、Laravelでは通常{{}}で囲んだ内容はタグがエスケープされますが、$slotに関してはエスケープされません。$slotが通常の文字列ではなく「Illuminate¥Support¥HtmlString」のインスタンスである事によって区別されているようです。$slotをstrvalで通常の文字列にキャストするとエスケープされます。
components¥form¥email.blade.php
...
{{-- ↓エスケープされない --}}
{{ $slot }}
{{-- ↓エスケープされる --}}
{{ strval($slot) }}
@php echo htmlspecialchars($slot); @endphp
...
スロットを分割したい場合はコンポーネントタグの内側で「x-slot:変数名」のタグを使うことで可能です。x-slotの閉じタグでは変数名は不要です。ここでの名づけもケバブケースとキャメルケースの関係にあります。ここで囲った部分は$slotには入らずに指定した名前に$をつけた変数名に入るようになります。
sample.blade.php
...
<x-form.email><h3>スロット</h3><x-slot:sub-slot>別の値</x-slot></x-form.email>
<!-- コンポーネントでは $subSlotで利用可能 -->
...
blade.phpのスロット部で、クラスベースコンポーネントで記述したのメソッドを利用することができます。これを「Scoped Slots」と呼びます。スロット内の{{}}では、コンポーネントを参照する$componentオブジェクトが用意されていますので、そこからメソッドを呼びだします。
sample.blade.php
...
<x-form.email><h3>スロット No.{{$component->getSlotNo();}}</h3></x-form.email>
...
名前を付けて細分化したスロットでは、受け取った変数から$attributesプロパティを使うことができます。
sample.blade.php
...
<x-form.email class="main-slot"><h3>スロット</h3><x-slot:sub-slot class="sub-slot">別の値</x-slot></x-form.email>
...
sample.component.blade.php
...
{{-- subSlotの中身が表示されます --}}
{{ $subSlot }}
{{-- x-slotタグで設定した属性が表示されます --}}
{{ $subSlot->attributes }}
コンポーネントを動的に決定
コンポーネント名を動的に指定したい場合は、x-dynamic-componentタグを使って、component属性にコンポーネント名を渡します。
その際に渡す値は通常変数となるため:componentとします。
sample.blade.php
...
<x-dynamic-component :component="$compName"/>
サービスを利用する
コンポーネントのテンプレート内でサービスのインスタンスが必要になった際、はQuiita:「便利なService(サービス)の使い方...」によれば、@injectを利用して次のように書くことができるそうです。
some.blade.php
@inject ('ServiceClass','App¥Services¥ServiceName')
こうすることでサービスのインスタンスは、$ServiceClass変数に入ります。ただ、筆者が試してみたところこの方法だとサービスプロバイダでシングルトンとして設定したインスタンスは取得できないようでした。
そのようなケースではクラスベースのコンポーネントにして、クラスのコンストラクタでサービスをタイプヒントさせた後、パブリックプロパティにセットすることで、blade.phpからプロパティ名でシングルトンのインスタンスにアクセスできるようになります。
classbase-component.blade.php
...
public $service;
public function __construct(ServiceName $service) {
$this->service = $service;
}
...