Laravel Blade テンプレートの書き方チートシート
記事の概要
Laravelに含まれているテンプレートエンジンBladeは大変便利ですが、多機能ゆえに意外と知らない書き方があったり、何度も同じ事を調べてしまいがちです。
こちらに網羅的な公式ドキュメントがありますが、タスキに長しという感があります。
この記事では筆者が業務でよく使用するものに絞り込んで、Bladeテンプレートの書き方をご紹介します。
PHP変数の展開
{{ $v }}
BladeテンプレートにPHP変数の中身を表示するには{{ $v }}
を使います。
この方法はただ変数の中身をHTMLに展開するだけでなく、セキュリティ対策として、ブラウザがHTMLタグとして解釈できないよう特定の文字をエスケープしてくれます。
例えば、$v
の中に<script>
が含まれていてもブラウザはJavascriptを実行しません。
{!! $v !!}
BladeテンプレートにPHP変数の中身をエスケープせずにそのまま表示するには{!! $v !!}
を使います。
しかし、びっくりマークが使われていることからもわかるように、この書き方にはリスクが伴います。
悪意のあるコードはどこにでも紛れ込みうるものと考えて、常に{{ $v }}
を使うべきです。
{!! nl2br(e($v)) !!}
{!! $v !!}
の使用を回避すべきことの例外として、PHP変数の改行文字"¥n"
を<br>
に変換して出力したいケースがあります。
{{ nl2br($v) }}
と書くと「<br>
」が文字としてそのまま表示されてしまいます。
そこで、e()
で特定の文字をエスケープしたのちに、nl2br()
を適用しそのまま出力することで、<br>
がエスケープされるのを回避します。
エスケープとは
ここでいうエスケープとは、上記例の$v
の中身がブラウザによってHTMLとして認識されないようにすることを言います。
例えば、$v
がユーザーが投稿したメッセージである場合、悪意あるコードを実行する<script>
タグが記述されている可能性があります。
これをブラウザが読み込んで実行する恐れがあるため、Bladeで変数の中身をHTMLに展開するときは、常にエスケープすることが推奨されます。
条件分岐
@if
PHPのif
と同じように表示内容を制御できます。条件分岐には様々なディレクティブが存在しますが、@if
で再現できるものが多いので、差し当たり@if
を知っていれば大丈夫でしょう。
@if (count($records) === 1)
一件のレコードが存在します!
@elseif (count($records) > 1)
複数件のレコードが存在します!
@else
レコードが存在しません!
@endif
@switch
PHPのswitch
と同じように表示内容を制御できます。
@break
がないと次の@case
以下のコードが実行されてしまうため、それを意図しない場合は@break
を忘れないようにしましょう。
自信がなければ@if
でも代替できるので、@switch
を無理に使う必要はありません。
@switch($i)
@case(1)
ケース1...
@break
@case(2)
ケース2...
@break
@default
その他のケース...
@endswitch
ループ
@for
PHPのfor
と同じように表示内容を繰り返すことができます。
@for ($i = 0; $i < 10; $i++)
<p>現在の値は {{ $i }}</p>
@endfor
@foreach
PHPのforeach
と同じように、配列・コレクションに含まれている要素の数だけ、表示内容を繰り返すことができます。ループ内では、取り出した値(以下の例では$user
)を使うことができます。
@foreach ($users as $index => $user)
<p>{{ $index }}番目のユーザーのIDは {{ $user->id }}</p>
@endforeach
@forelse
配列・コレクションが空のとき、その旨を表示したいことが多々あります。@foreach
と@if
を組み合わせても実装できますが@forelse
を使うと簡潔に記述できるのでオススメです。
@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>ユーザーが登録されていません</p>
@endforelse
$loop
ループ内では$loop
という特別な変数を宣言なしで使えます。$loop
は様々なプロパティを持っており、わざわざこれを使わなくても実装できることの方が多いですが、ご紹介しておきます。
以下は、1回目のループならばture、それ以外ならfalseであるfirst
プロパティを使った例です。
あとで紹介する@class
とTailewind CSSを併用して、最初の要素には上下に、それ以外の要素には下にだけボーダーを表示しています。
@foreach ($users as $user)
<p @class([
'border-y' => $loop->first,
'border-b' => !$loop->first,
])>このユーザーのIDは {{ $user->id }}</p>
@endforeach
条件付きクラス
@class
特にTailwind CSSを使っているとき、条件を満たすときだけクラスを指定したいことがあります。そういうときは@class
の出番です。
<span @class([
'p-4', {{-- 常にclassに含まれる --}}
'bg-red-500' => $hasError, {{-- $hasErrorが真のときclassに含まれる --}}
])>テキスト</span>
@style
@class
と同じように、style
属性に対しても条件指定できます。
<span @style([
'padding: 1rem', {{-- 常にstyleに含まれる --}}
'background-color: red' => $hasError, {{-- $hasErrorが真のときstyleに含まれる --}}
])>テキスト</span>
PHPコード
@php
@php
を使うことでBladeテンプレートにPHPコードを記述することができます。@php
ブロック内で定義した変数をブロック外で使うこともできます。
@php
$hasError = true;
@endphp
<span @class([
'p-4',
'bg-red-500' => $hasError,
])>テキスト</span>
@php()
@php()
を使えば@endphp
が不要なので、1行でコードを書けます。
@php(dd($hasError))
コメント
{{-- コメント --}}
Bladeテンプレート内でのコメントには{{-- コメント --}}
を使います。
HTMLのコメントである<!-- コメント -->
も使えますが、こちらはレンダーされたHTMLに残るため、開発者ツールを通してユーザーに晒されてしまいます。
バリデーション
@error()
formリクエストの際にPHP側のvalidate()
でエラーが生じたとき、エラーメッセージの表示には@error()
を使います。引数として対象となるフォームデータ名を渡します。
エラーメッセージはブロック内で使える特別な変数$message
に格納されています。
<input name="email" />
@error('email')
<span class="text-red-500">{{ $message }}</span>
@enderror
$request->validate([
'email' => 'required|email',
]);
コンポーネント
コンポーネントとは
コンポーネントとは、ボタンやヘッダーメニューなど複数ページで繰り返し使うBladeの記述を別ファイルに保存し、ページで呼び出して部品のように配置できる機能です。
コンポーネントには、クラスとBladeテンプレートの2ファイルから構成される通常のコンポーネントと、Bladeテンプレートだけで構成される匿名コンポーネントがあります。
匿名コンポーネントの方が管理しやすく、おそらく通常のコンポーネントにしかできないことはないので、筆者は匿名コンポーネントばかり使っています。
この記事では匿名コンポーネントの使い方に絞って解説します。
ファイルの作り方
以下のコマンドで作ることもできますが、/resources/views/components
配下に空のファイルを自分で作ることもできます。拡張子は.blade.php
です。
php artisan make:component フォルダ名.コンポーネント名 --view
配置方法
他のBladeテンプレートでコンポーネントを呼び出して配置するには、<x-フォルダ名.コンポーネント名/>
タグを記述します。例えば/resources/views/components/form/input.blade.php
の場合は<x-form.input/>
です。なお、コンポーネント内から他のコンポーネントを呼び出して配置することもできます。
データの受け渡し
コンポーネントが受け取るデータを定義するには@props
を使います。デフォルト値を指定することもできます。
/resources/views/components/proverb.blade.php
@props(['name', 'message' => '天は人の上に人を作らず'])
<div>
{{ $name }}曰く「{{ $message }}」
</div>
タグに属性を指定するようにして、コンポーネントにデータを渡すことができます。PHP変数を渡したい場合は、属性名の前に:
を付けます。
@php($message='学問は米をつきながらも出来るものなり')
<x-proverb name="福沢諭吉" :message="$message" /> {{-- 簡略形として:$messageとすることもできる --}}
注意点として、属性名の前に:
を付けることができるのは、コンポーネントだけであり、普通のHTMLタグの属性名の前に:を付けて変数を渡しても、その記述がそのままレンダーされるだけです。
$attributes
受け渡しする属性の名前を全て指定する必要はありません。加工したり変数展開する必要がなければ$attributes
でまとめて渡せます。
以下の例では$attributes
にx-on:click="submit" class="px-4 py-2"
の部分が展開されます。
/resources/views/components/submit-button.blade.php
<button {{ $attributes }}>送信</button>
<x-submit-button x-on:click="submit" class="px-4 py-2" />
$attributes->merge()
Tailwind CSSを使っていると、コンポーネント側で基本スタイルを指定しておき、配置するとき色だけ指定したいことがあります。単に属性を渡すだけだとclass
属性が2つになり衝突してしまうため、マージする必要があります。
/resources/views/components/submit-button.blade.php
<button {{ $attributes->merge(['class' => 'px-4 py-2']) }}>送信</button>
<x-submit-button class="bg-blue-200" />
注意点として、Tailewind CSSの同じ種類のクラスはマージされません。例えば、コンポーネントに基本スタイルとしてbg-red-200
が指定されておりbg-blue-200
が渡されきたとすると、class="bg-red-200 bg-blue-200"
のように展開されてしまい、どちらの色になるかわかりません。この問題に対処するには別パッケージの導入が必要ですが、この記事では立ち入りません。
$attributes->class()
class
属性のマージには簡略形があり、上記の例は以下のように記述することができます。
/resources/views/components/submit-button.blade.php
<button {{ $attributes->class('px-4 py-2') }}>送信</button>
さらにclass()
は@class
のように条件指定ができます。
@props(['hasError' => false])
<span {{ $attributes->class([
'p-4', {{-- 常にclassに含まれる --}}
'bg-red-500' => $hasError, {{-- $hasErrorが真のときclassに含まれる --}}
]) }}>テキスト</span>
$slot
HTMLタグと同じようにコンポーネントにも、文字やタグ、さらには別のコンポーネントを入れ子にすることができます。
<x-button>
<span class="text-red-500">絶対に押すな</span>
</x-button>
これをコンポーネントで表示するには$slot
という名前の変数を展開します。この変数は宣言なしで使えます。
/resources/views/components/button.blade.php
<button>{{ $slot }}</button>
x-slot:スロット名
複数のスロットが必要なら<x-slot:スロット名>
の出番です。以下の例ではheaderという名前の追加スロットを使用しています。
/resources/views/components/article.blade.php
<div {{ $attributes }}>
<header>{{ $header }}</header>
<main>{{ $slot }}</main>
</div>
<x-article>
<x-slot:header>タイトル</x-slot>
本文....
</x-article>
x-slot
に渡した属性を展開することもできます。もちろん$attributes
のメソッドも使えます。
/resources/views/components/article.blade.php
<div {{ $attributes }}>
<header {{ $header->attributes }}>{{ $header }}</header>
<main>{{ $slot }}</main>
</div>