- はじめに
- データの表示
- Bladeディレクティブ
- コンポーネント
- 匿名コンポーネント
- レイアウトの構築
- フォーム
- スタック
- サービス注入
- インラインBladeテンプレートのレンダリング
- Bladeフラグメントのレンダリング
- Bladeの拡張
#はじめに
BladeはLaravelに含まれるシンプルでありながら強力なテンプレートエンジンです。多くのPHPテンプレートエンジンとは異なり、Bladeはテンプレート内で通常のPHPコードを制限しません。実際、すべてのBladeテンプレートはプレーンなPHPコードにコンパイルされ、変更されるまでキャッシュされるため、Bladeはほぼオーバーヘッドなしで動作します。Bladeテンプレートファイルは.blade.php拡張子を使い、通常はresources/viewsディレクトリに保存されます。
Bladeビューはグローバルのviewヘルパーを使ってルートやコントローラーから返せます。もちろん、ビューのドキュメントで説明されているように、viewヘルパーの第2引数を使ってBladeビューにデータを渡せます。
Route::get('/', function () {
return view('greeting', ['name' => 'Finn']);
});
#LivewireでBladeを強化する
Bladeテンプレートをさらに進化させて、動的なインターフェースを簡単に構築したいですか?Laravel Livewireをチェックしてください。Livewireは、通常ReactやVueのようなフロントエンドフレームワークでしか実現できない動的機能を備えたBladeコンポーネントを作成できるため、多くのJavaScriptフレームワークの複雑さやクライアントサイドレンダリング、ビルドステップなしでモダンでリアクティブなフロントエンドを構築する優れた方法を提供します。
#データの表示
Bladeビューに渡されたデータは、変数を波括弧で囲むことで表示できます。例えば、次のルートがあるとします:
Route::get('/', function () {
return view('welcome', ['name' => 'Samantha']);
});
name変数の内容は次のように表示できます:
Hello, {{ $name }}.
Bladeの{{ }}エコーステートメントは自動的にPHPのhtmlspecialchars関数を通してXSS攻撃を防ぎます。
ビューに渡された変数の内容を表示するだけでなく、任意のPHP関数の結果をエコーできます。実際、Bladeのエコーステートメント内に任意のPHPコードを入れられます:
The current UNIX timestamp is {{ time() }}.
#HTMLエンティティのエンコード
デフォルトでは、Blade(およびLaravelのe関数)はHTMLエンティティを二重にエンコードします。二重エンコードを無効にしたい場合は、AppServiceProviderのbootメソッド内でBlade::withoutDoubleEncodingメソッドを呼び出してください:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::withoutDoubleEncoding();
}
}
#エスケープされていないデータの表示
デフォルトで、Bladeの{{ }}ステートメントはXSS攻撃を防ぐために自動的にPHPのhtmlspecialchars関数を通します。エスケープしたくない場合は、次の構文を使えます:
Hello, {!! $name !!}.
アプリケーションのユーザーから提供されたコンテンツをエコーする際は非常に注意してください。通常はXSS攻撃を防ぐためにエスケープされた二重波括弧構文を使うべきです。
#BladeとJavaScriptフレームワーク
多くのJavaScriptフレームワークも「波括弧」を使ってブラウザに表示する式を示すため、@記号を使ってBladeレンダリングエンジンに式をそのままにするよう指示できます。例えば:
<h1>Laravel</h1>
Hello, @{{ name }}.
この例では、@記号はBladeによって削除されますが、{{ name }}式はBladeエンジンに触れられず、JavaScriptフレームワークによってレンダリングされます。
@記号はBladeディレクティブのエスケープにも使えます:
{{-- Blade template --}}
@@if()
<!-- HTML 出力 -->
@if()
#JSONのレンダリング
配列をビューに渡してJavaScript変数の初期化のためにJSONとしてレンダリングしたい場合があります。例えば:
<script>
var app = <?php echo json_encode($array); ?>;
</script>
しかし、手動でjson_encodeを呼び出す代わりに、Illuminate\Support\Js::fromメソッドディレクティブを使えます。fromメソッドはPHPのjson_encodeと同じ引数を受け取りますが、HTMLの引用符内に含めるために適切にエスケープされたJSONを保証します。fromメソッドは、与えられたオブジェクトや配列を有効なJavaScriptオブジェクトに変換するJSON.parseのJavaScript文を文字列で返します:
<script>
var app = {{ Illuminate\Support\Js::from($array) }};
</script>
Laravelの最新のアプリケーションスケルトンにはJsファサードが含まれており、Bladeテンプレート内でこの機能に便利にアクセスできます:
<script>
var app = {{ Js::from($array) }};
</script>
既存の変数をJSONとしてレンダリングする場合にのみJs::fromメソッドを使うべきです。Bladeテンプレートは正規表現に基づいているため、複雑な式をディレクティブに渡すと予期しない失敗が起こる可能性があります。
#@verbatimディレクティブ
テンプレートの大部分でJavaScript変数を表示する場合、@verbatimディレクティブでHTMLを囲むことで、各Bladeエコーステートメントに@を付ける必要がなくなります:
@verbatim
<div class="container">
Hello, {{ name }}.
</div>
@endverbatim
#Bladeディレクティブ
テンプレート継承やデータ表示に加え、Bladeは条件文やループなどの一般的なPHP制御構造の便利なショートカットも提供します。これらのショートカットはPHPの対応する構造と同じ動作をしつつ、非常にシンプルで読みやすい書き方を可能にします。
#if文
@if、@elseif、@else、@endifディレクティブを使ってif文を構築できます。これらはPHPの対応する構造と同じ動作をします:
@if (count($records) === 1)
I have one record!
@elseif (count($records) > 1)
I have multiple records!
@else
I don't have any records!
@endif
便宜上、Bladeは@unlessディレクティブも提供します:
@unless (Auth::check())
You are not signed in.
@endunless
すでに説明した条件ディレクティブに加え、@issetと@emptyディレクティブはそれぞれ対応するPHP関数のショートカットとして使えます:
@isset($records)
// $recordsが定義されていてnullでない場合...
@endisset
@empty($records)
// $recordsが「空」の場合...
@endempty
#認証ディレクティブ
@authと@guestディレクティブは現在のユーザーが認証済みかゲストかを素早く判定できます:
@auth
// ユーザーは認証済みです...
@endauth
@guest
// ユーザーは認証されていません...
@endguest
必要に応じて、@authと@guestディレクティブでチェックする認証ガードを指定できます:
@auth('admin')
// ユーザーは認証済みです...
@endauth
@guest('admin')
// ユーザーは認証されていません...
@endguest
#環境ディレクティブ
@productionディレクティブを使ってアプリケーションが本番環境で動作しているか確認できます:
@production
// 本番環境専用のコンテンツ...
@endproduction
また、@envディレクティブを使って特定の環境で動作しているか判定できます:
@env('staging')
// アプリケーションは「staging」環境で動作しています...
@endenv
@env(['staging', 'production'])
// アプリケーションは「staging」または「production」環境で動作しています...
@endenv
#セクションディレクティブ
テンプレート継承のセクションにコンテンツがあるかどうかは@hasSectionディレクティブで判定できます:
@hasSection('navigation')
<div class="pull-right">
@yield('navigation')
</div>
<div class="clearfix"></div>
@endif
sectionMissingディレクティブを使うと、セクションにコンテンツがないかどうか判定できます:
@sectionMissing('navigation')
<div class="pull-right">
@include('default-navigation')
</div>
@endif
#セッションディレクティブ
@sessionディレクティブはセッションの値が存在するか判定できます。セッション値が存在する場合、@sessionと@endsessionの間のテンプレート内容が評価されます。@session内では$value変数をエコーしてセッション値を表示できます:
@session('status')
<div class="p-4 bg-green-100">
{{ $value }}
</div>
@endsession
#switch文
@switch、@case、@break、@default、@endswitchディレクティブを使ってswitch文を構築できます:
@switch($i)
@case(1)
First case...
@break
@case(2)
Second case...
@break
@default
Default case...
@endswitch
#ループ
条件文に加え、BladeはPHPのループ構造を扱う簡単なディレクティブも提供します。これらもPHPの対応する構造と同じ動作をします:
@for ($i = 0; $i < 10; $i++)
The current value is {{ $i }}
@endfor
@foreach ($users as $user)
<p>This is user {{ $user->id }}</p>
@endforeach
@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>No users</p>
@endforelse
@while (true)
<p>I'm looping forever.</p>
@endwhile
foreachループを繰り返す際、ループ変数を使うと、現在が最初の繰り返しか最後の繰り返しかなどの有用な情報を得られます。
ループ内で現在の繰り返しをスキップしたりループを終了したりするには、@continueと@breakディレクティブを使えます:
@foreach ($users as $user)
@if ($user->type == 1)
@continue
@endif
<li>{{ $user->name }}</li>
@if ($user->number == 5)
@break
@endif
@endforeach
ディレクティブ宣言内に継続や中断の条件を含めることもできます:
@foreach ($users as $user)
@continue($user->type == 1)
<li>{{ $user->name }}</li>
@break($user->number == 5)
@endforeach
#ループ変数
foreachループを繰り返す際、ループ内で$loop変数が利用可能です。この変数は現在のループインデックスや最初・最後の繰り返しかどうかなどの有用な情報を提供します:
@foreach ($users as $user)
@if ($loop->first)
This is the first iteration.
@endif
@if ($loop->last)
This is the last iteration.
@endif
<p>This is user {{ $user->id }}</p>
@endforeach
ネストしたループの場合、親ループの$loop変数にはparentプロパティ経由でアクセスできます:
@foreach ($users as $user)
@foreach ($user->posts as $post)
@if ($loop->parent->first)
This is the first iteration of the parent loop.
@endif
@endforeach
@endforeach
$loop変数には他にもさまざまな便利なプロパティがあります:
| プロパティ | 説明 |
|---|---|
$loop->index |
現在のループ繰り返しのインデックス(0から開始) |
$loop->iteration |
現在のループ繰り返し番号(1から開始) |
$loop->remaining |
ループの残り繰り返し数 |
$loop->count |
ループ対象の配列の総アイテム数 |
$loop->first |
最初の繰り返しかどうか |
$loop->last |
最後の繰り返しかどうか |
$loop->even |
偶数回目の繰り返しかどうか |
$loop->odd |
奇数回目の繰り返しかどうか |
$loop->depth |
現在のループのネストレベル |
$loop->parent |
ネストしたループの場合、親ループの変数 |
#条件付きクラスとスタイル
@class ディレクティブは、条件に応じて CSS クラスの文字列をコンパイルします。このディレクティブはクラスの配列を受け取り、配列のキーに追加したいクラスまたはクラス群を指定し、値には真偽値の式を設定します。配列要素のキーが数値の場合、そのクラスは常にレンダリングされるクラスリストに含まれます。
@php
$isActive = false;
$hasError = true;
@endphp
<span @class([
'p-4',
'font-bold' => $isActive,
'text-gray-500' => ! $isActive,
'bg-red' => $hasError,
])></span>
<span class="p-4 text-gray-500 bg-red"></span>
同様に、@style ディレクティブは HTML 要素に条件付きでインライン CSS スタイルを追加するために使用できます。
@php
$isActive = true;
@endphp
<span @style([
'background-color: red',
'font-weight: bold' => $isActive,
])></span>
<span style="background-color: red; font-weight: bold;"></span>
#追加属性
利便性のために、@checked ディレクティブを使って、指定した HTML チェックボックス入力が「checked」かどうかを簡単に示せます。このディレクティブは条件が true の場合に checked を出力します。
<input type="checkbox"
name="active"
value="active"
@checked(old('active', $user->active)) />
同様に、@selected ディレクティブは指定したセレクトオプションが「selected」かどうかを示すために使えます。
<select name="version">
@foreach ($product->versions as $version)
<option value="{{ $version }}" @selected(old('version') == $version)>
{{ $version }}
</option>
@endforeach
</select>
さらに、@disabled ディレクティブは指定した要素が「disabled」かどうかを示すために使えます。
<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>
また、@readonly ディレクティブは指定した要素が「readonly」かどうかを示すために使えます。
<input type="email"
name="email"
value="[email protected]"
@readonly($user->isNotAdmin()) />
加えて、@required ディレクティブは指定した要素が「required」かどうかを示すために使えます。
<input type="text"
name="title"
value="title"
@required($user->isAdmin()) />
#サブビューの読み込み
@include ディレクティブは自由に使えますが、Blade の コンポーネントは同様の機能を持ち、データや属性のバインディングなど @include より多くの利点があります。
Blade の @include ディレクティブは、あるビューの中から別の Blade ビューを読み込めます。親ビューで利用可能なすべての変数は、読み込まれたビューでも利用可能になります。
<div>
@include('shared.errors')
<form>
<!-- フォームの内容 -->
</form>
</div>
読み込まれたビューは親ビューのすべてのデータを継承しますが、追加で読み込まれたビューに渡したいデータの配列を指定することもできます。
@include('view.name', ['status' => 'complete'])
存在しないビューを @include しようとすると Laravel はエラーを投げます。存在するかどうかわからないビューを読み込みたい場合は、@includeIf ディレクティブを使うべきです。
@includeIf('view.name', ['status' => 'complete'])
特定のブール式が true または false と評価される場合にビューを @include したいときは、@includeWhen および @includeUnless ディレクティブを使用できます:
@includeWhen($boolean, 'view.name', ['status' => 'complete'])
@includeUnless($boolean, 'view.name', ['status' => 'complete'])
指定したビューの配列の中で最初に存在するビューを読み込みたい場合は、includeFirst ディレクティブを使えます。
@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])
Blade ビュー内で __DIR__ や __FILE__ 定数を使うのは避けてください。これらはキャッシュされたコンパイル済みビューの場所を指すためです。
#コレクションのためのビューのレンダリング
Blade の @each ディレクティブを使うと、ループとインクルードを一行で組み合わせられます。
@each('view.name', $jobs, 'job')
@each ディレクティブの第一引数は、配列やコレクションの各要素に対してレンダリングするビューです。第二引数は繰り返す配列やコレクションで、第三引数はビュー内で現在の繰り返し要素に割り当てる変数名です。例えば jobs の配列を繰り返す場合、ビュー内で各ジョブを job 変数としてアクセスしたいでしょう。現在の繰り返しの配列キーはビュー内で key 変数として利用可能です。
@each ディレクティブには第四引数も渡せます。この引数は、指定した配列が空の場合にレンダリングするビューを決定します。
@each('view.name', $jobs, 'job', 'view.empty')
@each でレンダリングされるビューは親ビューの変数を継承しません。子ビューでこれらの変数が必要な場合は、代わりに @foreach と @include を使うべきです。
#@once ディレクティブ
@once ディレクティブは、テンプレートの一部をレンダリングサイクルごとに一度だけ評価するために使えます。これは スタックを使ってページのヘッダーに JavaScript をプッシュする場合に便利です。例えばループ内で特定の コンポーネントをレンダリングする際、コンポーネントが初めてレンダリングされるときだけ JavaScript をヘッダーにプッシュしたい場合に使います。
@once
@push('scripts')
<script>
// カスタム JavaScript...
</script>
@endpush
@endonce
@once は @push や @prepend と一緒に使われることが多いため、利便性のために @pushOnce と @prependOnce ディレクティブも用意されています。
@pushOnce('scripts')
<script>
// カスタム JavaScript...
</script>
@endPushOnce
#生の PHP
場合によっては、ビュー内に PHP コードを埋め込むことが有用です。Blade の @php ディレクティブを使うと、テンプレート内でプレーンな PHP ブロックを実行できます。
@php
$counter = 1;
@endphp
また、クラスをインポートするだけなら @use ディレクティブを使えます。
@use('App\Models\Flight')
@use ディレクティブには第二引数を渡して、インポートしたクラスにエイリアスを付けることもできます。
@use('App\Models\Flight', 'FlightModel')
#コメント
Blade ではビュー内にコメントを定義できます。ただし、HTML コメントとは異なり、Blade コメントはアプリケーションが返す HTML に含まれません。
{{-- This comment will not be present in the rendered HTML --}}
#コンポーネント
コンポーネントとスロットは、セクション、レイアウト、インクルードと似た利点を提供しますが、コンポーネントとスロットのメンタルモデルの方が理解しやすいと感じる方もいます。コンポーネントの作成には、クラスベースコンポーネントと匿名コンポーネントの2つの方法があります。
クラスベースコンポーネントを作成するには、make:component Artisan コマンドを使います。コンポーネントの使い方を示すために、シンプルな Alert コンポーネントを作成します。make:component コマンドはコンポーネントを app/View/Components ディレクトリに配置します。
php artisan make:component Alert
make:component コマンドはコンポーネントのビュー・テンプレートも作成します。ビューは resources/views/components ディレクトリに配置されます。自分のアプリケーション用にコンポーネントを書く場合、app/View/Components と resources/views/components ディレクトリ内のコンポーネントは自動的に検出されるため、通常は追加の登録は不要です。
サブディレクトリ内にコンポーネントを作成することもできます。
php artisan make:component Forms/Input
上記のコマンドは app/View/Components/Forms ディレクトリに Input コンポーネントを作成し、ビューは resources/views/components/forms ディレクトリに配置します。
匿名コンポーネント(クラスなしで Blade テンプレートだけのコンポーネント)を作成したい場合は、make:component コマンド実行時に --view フラグを使います。
php artisan make:component forms.input --view
上記のコマンドは resources/views/components/forms/input.blade.php に Blade ファイルを作成し、<x-forms.input /> としてコンポーネントをレンダリングできます。
#パッケージコンポーネントの手動登録
自分のアプリケーション用にコンポーネントを書く場合、app/View/Components と resources/views/components ディレクトリ内のコンポーネントは自動的に検出されます。
しかし、Blade コンポーネントを使うパッケージを作成する場合は、コンポーネントクラスとその HTML タグのエイリアスを手動で登録する必要があります。通常はパッケージのサービスプロバイダーの boot メソッド内で登録します。
use Illuminate\Support\Facades\Blade;
/**
* パッケージのサービスをブートストラップします。
*/
public function boot(): void
{
Blade::component('package-alert', Alert::class);
}
コンポーネントを登録したら、そのタグエイリアスを使ってレンダリングできます。
<x-package-alert/>
または、componentNamespace メソッドを使って規約に従いコンポーネントクラスをオートロードできます。例えば Nightshade パッケージには Calendar と ColorPicker コンポーネントが Package\Views\Components 名前空間にある場合です。
use Illuminate\Support\Facades\Blade;
/**
* パッケージのサービスをブートストラップします。
*/
public function boot(): void
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}
これにより、package-name:: 構文を使ってベンダー名前空間でパッケージコンポーネントを利用できます。
<x-nightshade::calendar />
<x-nightshade::color-picker />
Blade はコンポーネント名をパスカルケースに変換して関連付けられたクラスを自動検出します。サブディレクトリもドット表記でサポートされます。
#コンポーネントのレンダリング
コンポーネントを表示するには、Blade テンプレート内で Blade コンポーネントタグを使います。Blade コンポーネントタグは x- で始まり、その後にコンポーネントクラス名のケバブケースが続きます。
<x-alert/>
<x-user-profile/>
コンポーネントクラスが app/View/Components ディレクトリの深い階層にある場合は、. 文字でディレクトリのネストを示せます。例えば app/View/Components/Inputs/Button.php にあるコンポーネントは次のようにレンダリングします。
<x-inputs.button/>
コンポーネントを条件付きでレンダリングしたい場合は、コンポーネントクラスに shouldRender メソッドを定義できます。shouldRender が false を返すとコンポーネントはレンダリングされません。
use Illuminate\Support\Str;
/**
* コンポーネントをレンダリングすべきかどうか
*/
public function shouldRender(): bool
{
return Str::length($this->message) > 0;
}
#コンポーネントへのデータ渡し
Blade コンポーネントには HTML 属性を使ってデータを渡せます。プリミティブな値は単純な HTML 属性文字列で渡せます。PHP 式や変数は、属性名の前に : を付けて渡します。
<x-alert type="error" :message="$message"/>
コンポーネントのデータ属性はすべてクラスのコンストラクターで定義すべきです。コンポーネントのすべての public プロパティは自動的にコンポーネントのビューで利用可能になります。render メソッドからビューにデータを渡す必要はありません。
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class Alert extends Component
{
/**
* コンポーネントインスタンスを作成します。
*/
public function __construct(
public string $type,
public string $message,
) {}
/**
* コンポーネントを表すビュー/内容を取得します。
*/
public function render(): View
{
return view('components.alert');
}
}
コンポーネントがレンダリングされると、コンポーネントの public 変数の内容を名前でエコーして表示できます。
<div class="alert alert-{{ $type }}">
{{ $message }}
</div>
#ケーシング
コンポーネントのコンストラクター引数は camelCase で指定し、HTML 属性で参照する際は kebab-case を使います。例えば、次のコンポーネントコンストラクターの場合:
/**
* コンポーネントインスタンスを作成します。
*/
public function __construct(
public string $alertType,
) {}
$alertType 引数は次のようにコンポーネントに渡せます。
<x-alert alert-type="danger" />
#短縮属性構文
コンポーネントに属性を渡す際、属性名が変数名と一致することが多いため、「短縮属性」構文も使えます。これは便利です。
{{-- Short attribute syntax... --}}
<x-profile :$userId :$name />
{{-- Is equivalent to... --}}
<x-profile :user-id="$userId" :name="$name" />
#属性レンダリングのエスケープ
Alpine.js のような JavaScript フレームワークもコロン付き属性を使うため、Blade に PHP 式ではないことを伝えるために二重コロン(::)プレフィックスを使えます。例えば、次のコンポーネントの場合:
<x-button ::class="{ danger: isDeleting }">
Submit
</x-button>
以下の HTML が Blade によってレンダリングされます。
<button :class="{ danger: isDeleting }">
Submit
</button>
#コンポーネントメソッド
コンポーネントテンプレートで public 変数が利用できるだけでなく、コンポーネントの public メソッドも呼び出せます。例えば、isSelected メソッドを持つコンポーネントを想像してください。
/**
* 指定されたオプションが現在選択されているか判定します。
*/
public function isSelected(string $option): bool
{
return $option === $this->selected;
}
このメソッドはコンポーネントテンプレート内で、メソッド名と同じ変数を呼び出すことで実行できます。
<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}">
{{ $label }}
</option>
#コンポーネントクラス内での属性とスロットへのアクセス
Blade コンポーネントでは、クラスの render メソッド内からコンポーネント名、属性、スロットにアクセスすることもできます。ただし、このデータにアクセスするには、コンポーネントの render メソッドからクロージャを返す必要があります。クロージャは唯一の引数として $data 配列を受け取ります。この配列にはコンポーネントに関する情報を提供する複数の要素が含まれます:
use Closure;
/**
* コンポーネントを表すビュー/コンテンツを取得します。
*/
public function render(): Closure
{
return function (array $data) {
// $data['componentName'];
// $data['attributes'];
// $data['slot'];
return '<div>Components content</div>';
};
}
componentName は x- プレフィックスの後にHTMLタグで使われている名前と同じです。例えば <x-alert /> の componentName は alert になります。attributes 要素にはHTMLタグにあったすべての属性が含まれます。slot 要素はコンポーネントのスロットの内容を持つ Illuminate\Support\HtmlString のインスタンスです。
クロージャは文字列を返す必要があります。返された文字列が既存のビュー名と一致すればそのビューがレンダリングされます。そうでなければ、返された文字列はインラインBladeビューとして評価されます。
#追加の依存関係
コンポーネントがLaravelのサービスコンテナから依存関係を必要とする場合、コンポーネントのデータ属性の前にそれらをリストアップすると、コンテナによって自動的に注入されます:
use App\Services\AlertCreator;
/**
* コンポーネントのインスタンスを作成します。
*/
public function __construct(
public AlertCreator $creator,
public string $type,
public string $message,
) {}
#属性/メソッドの非公開化
コンポーネントのテンプレートに公開メソッドやプロパティを変数として公開したくない場合、コンポーネントの $except 配列プロパティにそれらを追加できます:
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Alert extends Component
{
/**
* コンポーネントテンプレートに公開しないプロパティ/メソッド。
*
* @var array
*/
protected $except = ['type'];
/**
* コンポーネントのインスタンスを作成します。
*/
public function __construct(
public string $type,
) {}
}
#コンポーネント属性
データ属性をコンポーネントに渡す方法は既に見ましたが、時には class のようなコンポーネントの動作に必要ない追加のHTML属性を指定したい場合があります。通常、これらの追加属性はコンポーネントテンプレートのルート要素に渡したいです。例えば、次のように alert コンポーネントをレンダリングしたいとします:
<x-alert type="error" :message="$message" class="mt-4"/>
コンポーネントのコンストラクタに含まれないすべての属性は自動的にコンポーネントの「属性バッグ」に追加されます。この属性バッグは $attributes 変数を通じてコンポーネントで利用可能になります。すべての属性はこの変数を出力することでレンダリングできます:
<div {{ $attributes }}>
<!-- コンポーネントの内容 -->
</div>
コンポーネントタグ内で @env のようなディレクティブを使うことは現在サポートされていません。例えば <x-alert :live="@env('production')"/> はコンパイルされません。
#デフォルト/マージされた属性
属性にデフォルト値を指定したり、コンポーネントの属性に追加の値をマージしたい場合があります。そのために属性バッグの merge メソッドを使えます。このメソッドは、常に適用したいデフォルトのCSSクラスを定義するのに特に便利です:
<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
{{ $message }}
</div>
このコンポーネントが次のように使われていると仮定します:
<x-alert type="error" :message="$message" class="mb-4"/>
コンポーネントの最終的なレンダリングHTMLは次のようになります:
<div class="alert alert-error mb-4">
<!-- $message 変数の内容 -->
</div>
#条件付きクラスのマージ
条件が true の場合にクラスをマージしたいことがあります。これは class メソッドで実現でき、配列のキーに追加したいクラスを、値に真偽値の式を指定します。配列要素のキーが数値の場合は常にクラスリストに含まれます:
<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}>
{{ $message }}
</div>
他の属性をコンポーネントにマージしたい場合は、class メソッドに続けて merge メソッドをチェーンできます:
<button {{ $attributes->class(['p-4'])->merge(['type' => 'button']) }}>
{{ $slot }}
</button>
マージされた属性を受け取らない他のHTML要素で条件付きクラスをコンパイルしたい場合は、@class ディレクティブを使えます。
#クラス以外の属性のマージ
class 属性以外の属性をマージする場合、merge メソッドに渡した値は属性の「デフォルト」値とみなされます。ただし、class 属性とは異なり、これらの属性は注入された属性値とマージされず、上書きされます。例えば、button コンポーネントの実装は次のようになります:
<button {{ $attributes->merge(['type' => 'button']) }}>
{{ $slot }}
</button>
カスタムの type を指定してボタンコンポーネントをレンダリングする場合、コンポーネントを使う際に指定できます。指定がなければ button タイプが使われます:
<x-button type="submit">
Submit
</x-button>
この例の button コンポーネントのレンダリングHTMLは次のようになります:
<button type="submit">
Submit
</button>
class 以外の属性でデフォルト値と注入値を結合したい場合は、prepends メソッドを使えます。この例では data-controller 属性は常に profile-controller で始まり、追加の注入された data-controller 値はこのデフォルト値の後に配置されます:
<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}>
{{ $slot }}
</div>
#属性の取得とフィルタリング
filter メソッドで属性をフィルタリングできます。このメソッドはクロージャを受け取り、属性を保持したい場合は true を返す必要があります:
{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }}
便利なことに、whereStartsWith メソッドで指定した文字列で始まるキーを持つすべての属性を取得できます:
{{ $attributes->whereStartsWith('wire:model') }}
逆に、whereDoesntStartWith メソッドで指定した文字列で始まる属性を除外できます:
{{ $attributes->whereDoesntStartWith('wire:model') }}
first メソッドを使うと、属性バッグ内の最初の属性をレンダリングできます:
{{ $attributes->whereStartsWith('wire:model')->first() }}
属性がコンポーネントに存在するか確認したい場合は、has メソッドを使えます。このメソッドは属性名を引数に取り、属性が存在するかどうかの真偽値を返します:
@if ($attributes->has('class'))
<div>Class attribute is present</div>
@endif
has メソッドに配列を渡すと、すべての指定した属性が存在するかどうかを判定します:
@if ($attributes->has(['name', 'class']))
<div>All of the attributes are present</div>
@endif
hasAny メソッドは、指定した属性のいずれかが存在するかどうかを判定します:
@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
<div>One of the attributes is present</div>
@endif
特定の属性の値を取得したい場合は、get メソッドを使えます:
{{ $attributes->get('class') }}
#予約語
Bladeの内部でコンポーネントをレンダリングするために、いくつかのキーワードは予約されています。以下のキーワードはコンポーネント内で公開プロパティやメソッド名として定義できません:
datarenderresolveViewshouldRenderviewwithAttributeswithName
#スロット
コンポーネントに追加のコンテンツを渡すために「スロット」を使うことがよくあります。コンポーネントのスロットは $slot 変数を出力することでレンダリングされます。この概念を理解するために、alert コンポーネントが次のようなマークアップを持つと想定します:
<!-- /resources/views/components/alert.blade.php -->
<div class="alert alert-danger">
{{ $slot }}
</div>
コンポーネントにコンテンツを注入することで、slot にコンテンツを渡せます:
<x-alert>
<strong>Whoops!</strong> Something went wrong!
</x-alert>
コンポーネントが複数の異なるスロットを異なる場所でレンダリングする必要がある場合があります。alert コンポーネントを修正して「title」スロットの注入を許可しましょう:
<!-- /resources/views/components/alert.blade.php -->
<span class="alert-title">{{ $title }}</span>
<div class="alert alert-danger">
{{ $slot }}
</div>
名前付きスロットの内容は x-slot タグで定義できます。明示的な x-slot タグに含まれないコンテンツはすべて $slot 変数に渡されます:
<x-alert>
<x-slot:title>
Server Error
</x-slot>
<strong>Whoops!</strong> Something went wrong!
</x-alert>
スロットにコンテンツがあるかどうかを判定するには、スロットの isEmpty メソッドを呼び出せます:
<span class="alert-title">{{ $title }}</span>
<div class="alert alert-danger">
@if ($slot->isEmpty())
This is default content if the slot is empty.
@else
{{ $slot }}
@endif
</div>
さらに、hasActualContent メソッドでHTMLコメント以外の「実際の」コンテンツがスロットに含まれているかどうかを判定できます:
@if ($slot->hasActualContent())
The scope has non-comment content.
@endif
#スコープ付きスロット
VueのようなJavaScriptフレームワークを使ったことがあれば、「スコープ付きスロット」を知っているかもしれません。これはスロット内でコンポーネントのデータやメソッドにアクセスできる機能です。Laravelでも、コンポーネントに公開メソッドやプロパティを定義し、スロット内で $component 変数を通じてコンポーネントにアクセスすることで同様の動作が可能です。この例では、x-alert コンポーネントのクラスに公開された formatAlert メソッドがあると仮定します:
<x-alert>
<x-slot:title>
{{ $component->formatAlert('Server Error') }}
</x-slot>
<strong>Whoops!</strong> Something went wrong!
</x-alert>
#スロット属性
Bladeコンポーネントと同様に、スロットにもCSSクラス名などの追加の属性を割り当てられます:
<x-card class="shadow-sm">
<x-slot:heading class="font-bold">
Heading
</x-slot>
Content
<x-slot:footer class="text-sm">
Footer
</x-slot>
</x-card>
スロットの属性とやり取りするには、スロット変数の attributes プロパティにアクセスします。属性の操作方法についてはコンポーネント属性のドキュメントを参照してください:
@props([
'heading',
'footer',
])
<div {{ $attributes->class(['border']) }}>
<h1 {{ $heading->attributes->class(['text-lg']) }}>
{{ $heading }}
</h1>
{{ $slot }}
<footer {{ $footer->attributes->class(['text-gray-700']) }}>
{{ $footer }}
</footer>
</div>
#インラインコンポーネントビュー
非常に小さなコンポーネントの場合、コンポーネントクラスとビューのテンプレートの両方を管理するのは面倒に感じるかもしれません。そのため、render メソッドから直接コンポーネントのマークアップを返すことができます:
/**
* コンポーネントを表すビュー/コンテンツを取得します。
*/
public function render(): string
{
return <<<'blade'
<div class="alert alert-danger">
{{ $slot }}
</div>
blade;
}
#インラインビューコンポーネントの生成
インラインビューをレンダリングするコンポーネントを作成するには、make:component コマンド実行時に inline オプションを使えます:
php artisan make:component Alert --inline
#動的コンポーネント
どのコンポーネントをレンダリングするか実行時までわからない場合があります。このような場合、Laravel組み込みの dynamic-component コンポーネントを使い、実行時の値や変数に基づいてコンポーネントをレンダリングできます:
// $componentName = "secondary-button";
<x-dynamic-component :component="$componentName" class="mt-4" />
#コンポーネントの手動登録
コンポーネントの手動登録に関する以下のドキュメントは、主にビューコンポーネントを含むLaravelパッケージを作成している方向けです。パッケージを作成していない場合、この部分はあまり関係ありません。
自分のアプリケーション用にコンポーネントを書く場合、app/View/Components ディレクトリと resources/views/components ディレクトリ内のコンポーネントは自動的に検出されます。
しかし、Bladeコンポーネントを使うパッケージを作成したり、非標準のディレクトリにコンポーネントを置く場合は、コンポーネントクラスとHTMLタグのエイリアスを手動で登録し、Laravelにコンポーネントの場所を知らせる必要があります。通常はパッケージのサービスプロバイダーの boot メソッドで登録します:
use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;
/**
* パッケージのサービスをブートストラップします。
*/
public function boot(): void
{
Blade::component('package-alert', AlertComponent::class);
}
コンポーネントが登録されると、タグのエイリアスを使ってレンダリングできます:
<x-package-alert/>
#パッケージコンポーネントのオートロード
代わりに、componentNamespace メソッドを使って規約に従いコンポーネントクラスをオートロードできます。例えば、Nightshade パッケージに Calendar と ColorPicker コンポーネントが Package\Views\Components 名前空間にある場合:
use Illuminate\Support\Facades\Blade;
/**
* パッケージのサービスをブートストラップします。
*/
public function boot(): void
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}
これにより、package-name:: 構文を使ってベンダー名前空間でパッケージコンポーネントを利用できます:
<x-nightshade::calendar />
<x-nightshade::color-picker />
Bladeはコンポーネント名をパスカルケースに変換してクラスを自動検出します。サブディレクトリも「ドット」表記でサポートされます。
#匿名コンポーネント
インラインコンポーネントと同様に、匿名コンポーネントは単一ファイルでコンポーネントを管理する仕組みを提供します。ただし、匿名コンポーネントは単一のビュー ファイルを使用し、関連するクラスを持ちません。匿名コンポーネントを定義するには、resources/views/components ディレクトリに Blade テンプレートを配置するだけです。例えば、resources/views/components/alert.blade.php にコンポーネントを定義している場合、次のようにレンダリングできます。
<x-alert/>
components ディレクトリ内でコンポーネントがさらに深くネストされている場合は、. 文字を使って示せます。例えば、resources/views/components/inputs/button.blade.php にコンポーネントが定義されている場合、次のようにレンダリングできます。
<x-inputs.button/>
#匿名インデックスコンポーネント
コンポーネントが多数の Blade テンプレートで構成されている場合、コンポーネントのテンプレートを単一のディレクトリにまとめたいことがあります。例えば、次のようなディレクトリ構造の「accordion」コンポーネントを想像してください。
/resources/views/components/accordion.blade.php
/resources/views/components/accordion/item.blade.php
このディレクトリ構造により、accordion コンポーネントとそのアイテムを次のようにレンダリングできます。
<x-accordion>
<x-accordion.item>
...
</x-accordion.item>
</x-accordion>
しかし、x-accordion で accordion コンポーネントをレンダリングするには、「index」accordion コンポーネントのテンプレートを他の accordion 関連テンプレートと同じ accordion ディレクトリにネストせず、resources/views/components ディレクトリに置く必要がありました。
幸いなことに、Blade ではコンポーネントのテンプレートディレクトリ内に index.blade.php ファイルを置くことができます。index.blade.php テンプレートが存在すると、そのコンポーネントの「ルート」ノードとしてレンダリングされます。したがって、上記の例で示した同じ Blade 構文を使い続けられますが、ディレクトリ構造は次のように調整します。
/resources/views/components/accordion/index.blade.php
/resources/views/components/accordion/item.blade.php
#データプロパティ / 属性
匿名コンポーネントには関連するクラスがないため、どのデータを変数としてコンポーネントに渡し、どの属性をコンポーネントの属性バッグに入れるか区別する方法が気になるかもしれません。
コンポーネントの Blade テンプレートの先頭で @props ディレクティブを使い、どの属性をデータ変数として扱うか指定できます。コンポーネントの他のすべての属性は属性バッグで利用可能です。データ変数にデフォルト値を設定したい場合は、変数名を配列のキー、デフォルト値を配列の値として指定できます。
<!-- /resources/views/components/alert.blade.php -->
@props(['type' => 'info', 'message'])
<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
{{ $message }}
</div>
上記のコンポーネント定義に基づき、次のようにコンポーネントをレンダリングできます。
<x-alert type="error" :message="$message" class="mb-4"/>
#親データへのアクセス
子コンポーネント内で親コンポーネントのデータにアクセスしたい場合があります。そのような場合は @aware ディレクティブを使います。例えば、親 <x-menu> と子 <x-menu.item> からなる複雑なメニューコンポーネントを作成するとします。
<x-menu color="purple">
<x-menu.item>...</x-menu.item>
<x-menu.item>...</x-menu.item>
</x-menu>
<x-menu> コンポーネントは次のように実装できます。
<!-- /resources/views/components/menu/index.blade.php -->
@props(['color' => 'gray'])
<ul {{ $attributes->merge(['class' => 'bg-'.$color.'-200']) }}>
{{ $slot }}
</ul>
color プロップは親 (<x-menu>) にのみ渡されているため、<x-menu.item> 内では利用できません。しかし、@aware ディレクティブを使うと、<x-menu.item> 内でも利用可能にできます。
<!-- /resources/views/components/menu/item.blade.php -->
@aware(['color' => 'gray'])
<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
{{ $slot }}
</li>
@aware ディレクティブは、HTML 属性を通じて親コンポーネントに明示的に渡されていない親データにはアクセスできません。親コンポーネントに明示的に渡されていないデフォルトの @props 値は @aware でアクセスできません。
#匿名コンポーネントのパス
前述の通り、匿名コンポーネントは通常、resources/views/components ディレクトリに Blade テンプレートを置くことで定義します。ただし、デフォルトのパスに加えて他の匿名コンポーネントのパスを Laravel に登録したい場合もあります。
anonymousComponentPath メソッドは、匿名コンポーネントの場所の「パス」を第1引数に、コンポーネントを配置する「名前空間」を第2引数(省略可能)に受け取ります。通常、このメソッドはアプリケーションのサービスプロバイダーの boot メソッド内で呼び出します。
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::anonymousComponentPath(__DIR__.'/../components');
}
上記の例のようにプレフィックスなしでコンポーネントパスを登録すると、Blade コンポーネント内で対応するプレフィックスなしでレンダリングできます。例えば、登録したパスに panel.blade.php コンポーネントが存在する場合、次のようにレンダリングできます。
<x-panel />
anonymousComponentPath メソッドの第2引数にプレフィックスの「名前空間」を指定できます。
Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');
プレフィックスを指定すると、その「名前空間」内のコンポーネントは、レンダリング時にコンポーネント名の前に名前空間を付けて呼び出せます。
<x-dashboard::panel />
#レイアウトの構築
#コンポーネントを使ったレイアウト
ほとんどのウェブアプリケーションは複数のページで同じ一般的なレイアウトを維持します。すべてのビューでレイアウトの HTML を繰り返すのは非常に面倒で保守が難しくなります。幸い、レイアウトを単一のBlade コンポーネントとして定義し、アプリケーション全体で使うことが便利です。
#レイアウトコンポーネントの定義
例えば、「todo」リストアプリケーションを作るとします。次のような layout コンポーネントを定義できます。
<!-- resources/views/components/layout.blade.php -->
<html>
<head>
<title>{{ $title ?? 'Todo Manager' }}</title>
</head>
<body>
<h1>Todos</h1>
<hr/>
{{ $slot }}
</body>
</html>
#レイアウトコンポーネントの適用
layout コンポーネントを定義したら、そのコンポーネントを使う Blade ビューを作成できます。この例では、タスクリストを表示するシンプルなビューを定義します。
<!-- resources/views/tasks.blade.php -->
<x-layout>
@foreach ($tasks as $task)
{{ $task }}
@endforeach
</x-layout>
コンポーネントに注入されたコンテンツは、layout コンポーネント内のデフォルトの $slot 変数に渡されます。ご覧の通り、layout は $title スロットも尊重し、指定がなければデフォルトのタイトルを表示します。タスクリストビューからカスタムタイトルを注入するには、コンポーネントのドキュメントで説明した標準のスロット構文を使います。
<!-- resources/views/tasks.blade.php -->
<x-layout>
<x-slot:title>
Custom Title
</x-slot>
@foreach ($tasks as $task)
{{ $task }}
@endforeach
</x-layout>
レイアウトとタスクリストビューを定義したので、あとはルートから tasks ビューを返すだけです。
use App\Models\Task;
Route::get('/tasks', function () {
return view('tasks', ['tasks' => Task::all()]);
});
#テンプレート継承を使ったレイアウト
#レイアウトの定義
レイアウトは「テンプレート継承」でも作成できます。これはコンポーネント導入前の主なアプリケーション構築方法でした。
まずは簡単な例を見てみましょう。ページレイアウトを確認します。ほとんどのウェブアプリケーションは複数ページで同じ一般的なレイアウトを維持するため、このレイアウトを単一の Blade ビューとして定義するのが便利です。
<!-- resources/views/layouts/app.blade.php -->
<html>
<head>
<title>App Name - @yield('title')</title>
</head>
<body>
@section('sidebar')
This is the master sidebar.
@show
<div class="container">
@yield('content')
</div>
</body>
</html>
このファイルは典型的な HTML マークアップを含みますが、@section と @yield ディレクティブに注目してください。@section は名前の通りコンテンツのセクションを定義し、@yield は指定したセクションの内容を表示します。
レイアウトを定義したので、次にそのレイアウトを継承する子ページを定義しましょう。
#レイアウトの拡張
子ビューを定義するときは、@extends Blade ディレクティブでどのレイアウトを「継承」するか指定します。Blade レイアウトを拡張するビューは、@section ディレクティブを使ってレイアウトのセクションにコンテンツを注入できます。前述の例のように、これらのセクションの内容はレイアウト内で @yield によって表示されます。
<!-- resources/views/child.blade.php -->
@extends('layouts.app')
@section('title', 'Page Title')
@section('sidebar')
@@parent
<p>This is appended to the master sidebar.</p>
@endsection
@section('content')
<p>This is my body content.</p>
@endsection
この例では、sidebar セクションが @@parent ディレクティブを使って、レイアウトのサイドバーの内容を上書きせずに追記しています。@@parent はビューがレンダリングされる際にレイアウトの内容に置き換わります。
前の例とは異なり、この sidebar セクションは @show ではなく @endsection で終了しています。@endsection はセクションを定義するだけで、@show はセクションを定義して即座に出力します。
@yield ディレクティブは第2引数にデフォルト値を受け取れます。これは、指定したセクションが未定義の場合にレンダリングされます。
@yield('content', 'Default content')
#フォーム
#CSRF フィールド
アプリケーションで HTML フォームを定義する際は、CSRF 保護ミドルウェアがリクエストを検証できるように、フォームに隠し CSRF トークンフィールドを含める必要があります。@csrf Blade ディレクティブを使ってトークンフィールドを生成できます。
<form method="POST" action="/profile">
@csrf
...
</form>
#メソッドフィールド
HTML フォームは PUT、PATCH、DELETE リクエストを直接送信できないため、これらの HTTP 動詞を偽装する隠し _method フィールドを追加する必要があります。@method Blade ディレクティブがこのフィールドを生成します。
<form action="/foo/bar" method="POST">
@method('PUT')
...
</form>
#バリデーションエラー
@error ディレクティブは、特定の属性に対するバリデーションエラーメッセージが存在するかを簡単にチェックできます。@error 内では $message 変数を表示してエラーメッセージを出力します。
<!-- /resources/views/post/create.blade.php -->
<label for="title">Post Title</label>
<input id="title"
type="text"
class="@error('title') is-invalid @enderror">
@error('title')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
@error は "if" 文にコンパイルされるため、属性にエラーがない場合にコンテンツをレンダリングするために @else ディレクティブを使えます。
<!-- /resources/views/auth.blade.php -->
<label for="email">Email address</label>
<input id="email"
type="email"
class="@error('email') is-invalid @else is-valid @enderror">
複数フォームを含むページでバリデーションエラーメッセージを取得するために、@error ディレクティブの第2引数に特定のエラーバッグ名を渡せます。
<!-- /resources/views/auth.blade.php -->
<label for="email">Email address</label>
<input id="email"
type="email"
class="@error('email', 'login') is-invalid @enderror">
@error('email', 'login')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
#スタック
Blade では名前付きスタックにコンテンツをプッシュし、別のビューやレイアウトの別の場所でレンダリングできます。これは子ビューで必要な JavaScript ライブラリを指定するのに特に便利です。
@push('scripts')
<script src="/example.js"></script>
@endpush
もし特定のブール式が true と評価された場合にコンテンツを @push したい場合は、@pushIf ディレクティブを使用できます。
@pushIf($shouldPush, 'scripts')
<script src="/example.js"></script>
@endPushIf
スタックには必要なだけ何度でもプッシュできます。スタックの全内容をレンダリングするには、スタック名を @stack ディレクティブに渡します。
<head>
<!-- ヘッドの内容 -->
@stack('scripts')
</head>
スタックの先頭にコンテンツを追加したい場合は、@prepend ディレクティブを使います。
@push('scripts')
This will be second...
@endpush
// 後で...
@prepend('scripts')
This will be first...
@endprepend
#サービス注入
@inject ディレクティブは、Laravel の サービスコンテナ からサービスを取得するために使えます。@inject に渡す最初の引数はサービスを格納する変数名で、2番目の引数は解決したいサービスのクラス名またはインターフェイス名です。
@inject('metrics', 'App\Services\MetricsService')
<div>
Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
</div>
#インライン Blade テンプレートのレンダリング
生の Blade テンプレート文字列を有効な HTML に変換する必要がある場合があります。これは Blade ファサードの render メソッドを使って実現できます。render メソッドは Blade テンプレート文字列と、テンプレートに渡す任意のデータ配列を受け取ります。
use Illuminate\Support\Facades\Blade;
return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);
Laravel はインライン Blade テンプレートを storage/framework/views ディレクトリに書き込みながらレンダリングします。レンダリング後にこれらの一時ファイルを削除したい場合は、deleteCachedView 引数をメソッドに渡せます。
return Blade::render(
'Hello, {{ $name }}',
['name' => 'Julian Bashir'],
deleteCachedView: true
);
#Blade フラグメントのレンダリング
Turbo や htmx のようなフロントエンドフレームワークを使う場合、HTTPレスポンス内で Blade テンプレートの一部だけを返したいことがあります。Blade の「フラグメント」はそれを可能にします。まず、Blade テンプレートの一部を @fragment と @endfragment ディレクティブで囲みます。
@fragment('user-list')
<ul>
@foreach ($users as $user)
<li>{{ $user->name }}</li>
@endforeach
</ul>
@endfragment
このテンプレートを使うビューをレンダリングするときに、fragment メソッドを呼び出して指定したフラグメントだけをHTTPレスポンスに含めるようにできます。
return view('dashboard', ['users' => $users])->fragment('user-list');
fragmentIf メソッドは、条件に応じてビューのフラグメントを返すか、そうでなければビュー全体を返します。
return view('dashboard', ['users' => $users])
->fragmentIf($request->hasHeader('HX-Request'), 'user-list');
fragments と fragmentsIf メソッドは複数のビュー・フラグメントをレスポンスで返せます。フラグメントは連結されます。
view('dashboard', ['users' => $users])
->fragments(['user-list', 'comment-list']);
view('dashboard', ['users' => $users])
->fragmentsIf(
$request->hasHeader('HX-Request'),
['user-list', 'comment-list']
);
#Blade の拡張
Blade では directive メソッドを使って独自のカスタムディレクティブを定義できます。Blade コンパイラがカスタムディレクティブを検出すると、ディレクティブ内の式を引数にして指定されたコールバックを呼び出します。
以下の例では、@datetime($var) ディレクティブを作成し、DateTime インスタンスである $var をフォーマットします。
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* アプリケーションのサービスを登録します。
*/
public function register(): void
{
// ...
}
/**
* アプリケーションのサービスを起動します。
*/
public function boot(): void
{
Blade::directive('datetime', function (string $expression) {
return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
});
}
}
ご覧の通り、ディレクティブに渡された式に対して format メソッドをチェーンしています。この例で生成される最終的な PHP は以下の通りです。
<?php echo ($var)->format('m/d/Y H:i'); ?>
Blade ディレクティブのロジックを更新した後は、キャッシュされた Blade ビューをすべて削除する必要があります。キャッシュされたビューは view:clear Artisan コマンドで削除できます。
#カスタムエコーハンドラー
Bladeでオブジェクトを「echo」しようとすると、オブジェクトの__toStringメソッドが呼び出されます。__toStringメソッドは、PHPに組み込まれた「マジックメソッド」の一つです。ただし、操作しているクラスがサードパーティ製ライブラリのものである場合など、特定のクラスの__toStringメソッドを制御できないことがあります。
そのような場合、Blade では特定のオブジェクト型に対してカスタムエコーハンドラーを登録できます。これを行うには、Blade の stringable メソッドを呼び出します。stringable はクロージャを受け取り、そのクロージャはレンダリング対象のオブジェクト型を型ヒントします。通常、stringable はアプリケーションの AppServiceProvider クラスの boot メソッド内で呼び出します。
use Illuminate\Support\Facades\Blade;
use Money\Money;
/**
* アプリケーションのサービスを起動します。
*/
public function boot(): void
{
Blade::stringable(function (Money $money) {
return $money->formatTo('en_GB');
});
}
カスタムエコーハンドラーを定義したら、Blade テンプレート内で単にオブジェクトを echo できます。
Cost: {{ $money }}
#カスタム If 文
単純なカスタム条件文を定義する場合、カスタムディレクティブを作るよりも複雑になることがあります。そのため、Blade ではクロージャを使って簡単にカスタム条件ディレクティブを定義できる Blade::if メソッドを提供しています。例えば、アプリケーションの設定されたデフォルトの "disk" をチェックするカスタム条件を AppServiceProvider の boot メソッドで定義できます。
use Illuminate\Support\Facades\Blade;
/**
* アプリケーションのサービスを起動します。
*/
public function boot(): void
{
Blade::if('disk', function (string $value) {
return config('filesystems.default') === $value;
});
}
カスタム条件を定義したら、テンプレート内で以下のように使えます。
@disk('local')
<!-- アプリケーションは local ディスクを使用しています... -->
@elsedisk('s3')
<!-- アプリケーションは s3 ディスクを使用しています... -->
@else
<!-- アプリケーションは他のディスクを使用しています... -->
@enddisk
@unlessdisk('local')
<!-- アプリケーションは local ディスクを使用していません... -->
@enddisk