Blade 模板
Blade 模板
Blade 模板
簡介
Blade 是 Laravel 提供的一個(gè)簡單而又強(qiáng)大的模板引擎。和其他流行的 PHP 模板引擎不同,Blade 并不限制你在視圖中使用原生 PHP 代碼。所有 Blade 視圖文件都將被編譯成原生的 PHP 代碼并緩存起來,除非它被修改,否則不會(huì)重新編譯,這就意味著 Blade 基本上不會(huì)給你的應(yīng)用增加任何負(fù)擔(dān)。Blade 視圖文件使用 .blade.php
作為文件擴(kuò)展名,被存放在 resources/views
目錄。
模板繼承
定義布局
Blade 的兩個(gè)主要優(yōu)點(diǎn)是模板繼承
和區(qū)塊
。為方便入門,讓我們先通過一個(gè)簡單的例子來上手。首先,我們來研究一個(gè)「主」頁面布局。因?yàn)榇蠖鄶?shù) web 應(yīng)用會(huì)在不同的頁面中使用相同的布局方式,因此可以很方便地定義單個(gè) 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 語法。不過,請(qǐng)注意 @section
和 @yield
指令。 @section
指令定義了視圖的一部分內(nèi)容,而 @yield
指令是用來顯示指定部分的內(nèi)容。
現(xiàn)在,我們已經(jīng)定義好了這個(gè)應(yīng)用程序的布局,接下來,我們定義一個(gè)繼承此布局的子頁面。
擴(kuò)展布局
在定義一個(gè)子視圖時(shí),使用 Blade 的 @extends
指令指定子視圖要「繼承」的視圖。擴(kuò)展自 Blade 布局的視圖可以使用 @section
指令向布局片段注入內(nèi)容。就如前面的示例中所示,這些片段的內(nèi)容將由布局中的顯示在布局中 @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
在這個(gè)示例中, sidebar
片段利用 @parent
指令向布局的 sidebar 追加(而非覆蓋)內(nèi)容。 在渲染視圖時(shí),@parent
指令將被布局中的內(nèi)容替換。
{tip} 和上一個(gè)示例相反,這里的
sidebar
片段使用@endsection
代替@show
來結(jié)尾。@endsection
指令僅定義了一個(gè)片段,@show
則在定義的同時(shí) 立即 yield 這個(gè)片段。
Blade 視圖可以使用全局 view
助手自路由中返回:
Route::get('blade', function () { return view('child'); });
組件 & 插槽
組件和插槽提供了與片段和布局類似的好處;不過組件和插槽的思維模型更易于理解。我們先來看一個(gè)可復(fù)用的「alert」組件,我們想在應(yīng)用中復(fù)用它:
<!-- /resources/views/alert.blade.php --> <div class="alert alert-danger"> {{ $slot }} </div>
{{ $slot }}
變量將包含我們想要注入到組件的內(nèi)容?,F(xiàn)在,我們使用 Blade 的 @component
指令構(gòu)建這個(gè)組件:
@component('alert') <strong>Whoops!</strong> Something went wrong! @endcomponent
有時(shí)候?yàn)橐粋€(gè)組件定義多個(gè)插槽是很有用的。修改 alert 組件以允許其注入 「title」。命名插槽可以通過與其匹配的 「回顯」 變量顯示:
<!-- /resources/views/alert.blade.php --> <div class="alert alert-danger"> <div class="alert-title">{{ $title }}</div> {{ $slot }} </div>
現(xiàn)在,我們能夠使用 @slot
指令向命名插槽注入內(nèi)容。不在 @slot
指令內(nèi)的內(nèi)容都將傳遞給組件中的 $slot
變量:
@component('alert') @slot('title') Forbidden @endslot You are not allowed to access this resource!@endcomponent
向組件傳遞額外的數(shù)據(jù)
有時(shí)你可能需要向組件傳遞額外的數(shù)據(jù)。在這種情況下,可以把包含數(shù)據(jù)組織成數(shù)組,作為 @component
指令的第二個(gè)參數(shù)。所有的數(shù)據(jù)將作為變更提供給組件模板:
@component('alert', ['foo' => 'bar']) ... @endcomponent
給組件起別名
如果組件存儲(chǔ)在子目錄中,你可能希望給它們起個(gè)別名以方便訪問。舉例來說,如果一個(gè) Blade 組件存儲(chǔ)在 resources/views/components/alert.blade.php
中,. 就可以使用 component
方法將 components.alert
的別名命名為 alert
。. 通常情況下,這一過程將在 AppServiceProvider
的 boot
方法中完成:
use Illuminate\Support\Facades\Blade; Blade::component('components.alert', 'alert');
一旦組件有了別名,就可以使用一條指令渲染它:
@alert(['type' => 'danger']) You are not allowed to access this resource!@endalert
如果沒有額外的插槽,還可以省略組件參數(shù):
@alert You are not allowed to access this resource!@endalert
顯示數(shù)據(jù)
可以通過包裹在雙花括號(hào)內(nèi)的變量顯示傳遞給 Blade 視圖的數(shù)據(jù)。比如給出如下路由:
Route::get('greeting', function () { return view('welcome', ['name' => 'Samantha']); });
就可以這樣利用 name
變量顯示其內(nèi)容:
Hello, {{ $name }}.
{tip} Blade
{{ }}
語句是自動(dòng)經(jīng)過 PHP 的htmlspecialchars
函數(shù)傳遞來防范 XSS 攻擊的。
不限于顯示傳遞給視圖的變量的內(nèi)容,你還可以顯示任一 PHP 函數(shù)的結(jié)果。實(shí)際上,你可以在 Blade 的回顯語句中放置你想要的任意 PHP 代碼:
The current UNIX timestamp is {{ time() }}.
顯示非轉(zhuǎn)義字符
默認(rèn)情況下, Blade 中 {{ }}
語句自動(dòng)經(jīng)由 PHP 的 htmlspecialchars
函數(shù)傳遞以防范 XSS 攻擊。如果不希望數(shù)據(jù)被轉(zhuǎn)義,可以使用下面的語法:
Hello, {!! $name !!}.
{note} 在回顯應(yīng)用的用戶提供的內(nèi)容時(shí)需要謹(jǐn)慎小心。在顯示用戶提供的數(shù)據(jù)時(shí),有必要一直使用雙花括號(hào)語法轉(zhuǎn)義來防范 XSS 攻擊。
渲染 JSON
有時(shí),為了初始化一個(gè) JavaScript 變量,你可能會(huì)向視圖傳遞一個(gè)數(shù)據(jù),并將其渲染成 JSON:
<script> var app = <?php echo json_encode($array); ?>; </script>
不過,你可以使用 @json
Blade 指令代替手動(dòng)調(diào)用 json_encode
函數(shù):
<script> var app = @json($array); </script>
HTML 實(shí)體編碼
默認(rèn)情況下,Blade (以及 Laravel 的 e
助手)將對(duì) HTML 實(shí)體雙重編碼。如果要禁用雙重編碼,可以在 AppServiceProvider
的 boot
中調(diào)用 Blade::withoutDoubleEncoding
方法:
<?php namespace App\Providers; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider{ /** * 引導(dǎo)任意應(yīng)用服務(wù)。 * * @return void */ public function boot() { Blade::withoutDoubleEncoding(); } }
Blade & JavaScript 框架
由于很多 JavaScript 框架也使用花括號(hào)表明給定的表達(dá)式將要在瀏覽器中顯示, 可以使用 @
符號(hào)通知 Blade 渲染引擎某個(gè)表達(dá)式應(yīng)保持不變。示例如下:
<h1>Laravel</h1>Hello, @{{ name }}.
在這個(gè)例子中, @
符號(hào)將被 Blade 刪除;在 Blade 引擎中 {{ name }}
表達(dá)式將保持不變,取而代之的是 JavaScript 引擎將渲染該表達(dá)式。
@verbatim
指令
如果要在大段的模板中 JavaScript 變量,可以將 HTML 包裹在 @verbatim
指令中,這樣就不需要為每個(gè) Blade 回顯語句添加 @
符號(hào):
@verbatim <div class="container"> Hello, {{ name }}. </div>@endverbatim
控制結(jié)構(gòu)
除了模板繼承和數(shù)據(jù)顯示, Blade 還為分支和循環(huán)等 PHP 控制結(jié)構(gòu)提供了方便的快捷方式。這些快捷方式提供了干凈、簡捷地處理 PHP 控制結(jié)構(gòu)的方法,同時(shí)保持了與 PHP 中的對(duì)應(yīng)結(jié)構(gòu)的相似性。
If 語句
可以使用 @if
、 @elseif
、 @else
和 @endif
指令構(gòu)造 if
語句。這些指令的功能與相應(yīng)的 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
除了已經(jīng)討論過的條件指令, @isset
和 @empty
指令可以作為各自對(duì)應(yīng)的 PHP 函數(shù)的快捷方式:
@isset($records) // $records 被定義且不是 null... @endisset @empty($records) // $records 為空... @endempty
身份驗(yàn)證指令
@auth
和 @guest
指令能夠用于快速確定當(dāng)前用戶是經(jīng)過身份驗(yàn)證的,還是一個(gè)訪客:
@auth // 此用戶身份已驗(yàn)證...@endauth @guest // 此用戶身份未驗(yàn)證...@endguest
如果需要,可以在使用 @auth
和 @guest
指令時(shí)指定應(yīng)被校驗(yàn)的 身份 :
@auth('admin') // 此用戶身份已驗(yàn)證...@endauth @guest('admin') // 此用戶身份未驗(yàn)證...@endguest
片段指令
可以使用 @hasSection
指令檢查片斷是否存在內(nèi)容:
@hasSection('navigation') <div class="pull-right"> @yield('navigation') </div> <div class="clearfix"> </div> @endif
Switch 指令
可以使用 @switch
、 @case
、 @break
、 @default
和 @endswitch
指令構(gòu)造 switch 語句:
@switch($i) @case(1) First case... @break @case(2) Second case... @break @default Default case...@endswitch
循環(huán)
除了分支語句,Blade 還提供了與 PHP 的循環(huán)結(jié)構(gòu)相同的簡化指令。這些指令的功能也與相應(yīng)的 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
{tip} 循環(huán)中可以使用 循環(huán)變量 獲取循環(huán)的可評(píng)估信息,比如現(xiàn)在是處于循環(huán)的第一次迭代還是最后一次迭代中:
在循環(huán)中,還可以終結(jié)循環(huán)或路過本次迭代:
@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
循環(huán)變量
循環(huán)過程中,在循環(huán)體內(nèi)有一個(gè)可用的 $loop
變量。該變量提供了用于訪問諸如當(dāng)前循環(huán)的索引、當(dāng)前是否為第一次或最后一次循環(huán)之類的少數(shù)有用的信息的途徑:
@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
在嵌套循環(huán)中,可以借助 parent
屬性訪問父循環(huán)的 $loop
變量:
@foreach ($users as $user) @foreach ($user->posts as $post) @if ($loop->parent->first) This is first iteration of the parent loop. @endif @endforeach@endforeach
$loop
變量還包含其它幾種有用的屬性:
屬性 | 描述 |
---|---|
$loop->index | 當(dāng)前迭代的索引(從 0 開始計(jì)數(shù))。 |
$loop->iteration | 當(dāng)前循環(huán)迭代 (從 1 開始計(jì)算)。 |
$loop->remaining | 循環(huán)中剩余迭代的數(shù)量。 |
$loop->count | 被迭代的數(shù)組元素的總數(shù)。 |
$loop->first | 是否為循環(huán)的第一次迭代。 |
$loop->last | 是否為循環(huán)的最后一次迭代。 |
$loop->depth | 當(dāng)前迭代的嵌套深度級(jí)數(shù)。 |
$loop->parent | 嵌套循環(huán)中,父循環(huán)的循環(huán)變量 |
注釋
Blade 也允許在視圖中定義注釋。不過與 HTML 注釋不同,Blade 注釋不會(huì)包含在返回給應(yīng)用的 HTML 中:
{{-- This comment will not be present in the rendered HTML --}}
PHP
某些情況下,在視圖中嵌入 PHP 代碼很有用。可以在模板中使用 @php
指令執(zhí)行原生的 PHP 代碼塊:
@php //@endphp
{tip} 盡管 Blade 提供了這個(gè)特性,但頻繁使用意味著模板中嵌入了過多的邏輯。
表單
CSRF 域
只要在應(yīng)用中定義了 HTML 表單,就一定要在表單中包含隱藏的 CSRF 令牌域,這樣一來 CSRF 保護(hù) 中間件就能校驗(yàn)請(qǐng)求??梢允褂?Blade 的 @csrf
指令生成令牌域:
<form method="POST" action="/profile"> @csrf ... </form>
Method 域
HTML 表單不能發(fā)出 PUT
、 PATCH
及 DELETE
請(qǐng)求,需要加入隱藏的 _method
域來模仿這些 HTTP 動(dòng)詞。Blade 的 @method
指令能夠幫你創(chuàng)建這個(gè)域:
<form action="/foo/bar" method="POST"> @method('PUT') ... </form>
引入子視圖
Blade 的 @include
指令允許你從其它視圖中引入 Blade 視圖。父視圖中所有可用的變量都將在被引入的視圖中可用:
<div> @include('shared.errors') <form> <!-- Form Contents --> </form> </div>
被包含的視圖不僅會(huì)繼承父視圖的所有可用數(shù)據(jù),還能夠以數(shù)組形式向被包含的視圖傳遞額外數(shù)據(jù):
@include('view.name', ['some' => 'data'])
如果傳遞給 @include
一個(gè)不存在的視圖,Laravel 會(huì)拋出錯(cuò)誤。想要包含一個(gè)不能確定存在與否的視圖,需要使用 @includeIf
指令:
@includeIf('view.name', ['some' => 'data'])
想要包含一個(gè)依賴于給定布爾條件的視圖,可以使用 @includeWhen
指令:
@includeWhen($boolean, 'view.name', ['some' => 'data'])
要包含給定視圖數(shù)組中第一個(gè)存在的視圖,可以使用 includeFirst
指令:
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])
{note} 應(yīng)當(dāng)盡量避免在 Blade 視圖中使用
__DIR__
和__FILE__
魔術(shù)常量,因?yàn)樗鼈儗⒅赶蚓彺嬷薪?jīng)過編譯的視圖的位置。
給被包含的視圖起別名
如果你的 Blade 被包含視圖們存儲(chǔ)在子目錄中,你可能會(huì)希望為它們起個(gè)易于訪問的別名。例如,一個(gè)帶有如下內(nèi)容的 Blade 視圖內(nèi)容被存儲(chǔ)在 resources/views/includes/input.blade.php
文件中:
<input type="{{ $type ?? 'text' }}">
可以使用 include
方法為 includes.input
起一個(gè)叫做 input
的別名。通常,這會(huì)在 AppServiceProvider
的 boot
方法中完成:
use Illuminate\Support\Facades\Blade; Blade::include('includes.input', 'input');
一旦被包含的視圖擁有了別名,就可以像 Blade 指令一樣使用別名渲染它:
@input(['type' => 'email'])
為集合渲染視圖
可以使用 Blade 的 @each
指令在一行中整合循環(huán)和包含:
@each('view.name', $jobs, 'job')
第一個(gè)參數(shù)是渲染數(shù)組或集合的每個(gè)元素的視圖片段。第二個(gè)參數(shù)是希望被迭代的數(shù)組或集合,第三個(gè)參數(shù)則是將被分配給視圖中當(dāng)前迭代的變量名。例如,想要迭代 jobs
數(shù)組,通常會(huì)在視圖片段中使用 job
變量訪問每個(gè)任務(wù)。當(dāng)前迭代的 key 將作為視圖片段中的 key
變量。
也可以向 @each
指令傳遞第四個(gè)參數(shù)。這個(gè)參數(shù)是當(dāng)給定數(shù)組為空時(shí)要渲染的視圖片段。
@each('view.name', $jobs, 'job', 'view.empty')
{note} 借助
@each
渲染視圖,無法從父視圖中繼承變量。如果子視圖需要這些變量,就必須使用@foreach
和@include
代替它。
堆棧
Blade 允許你將視圖壓入堆棧,這些視圖能夠在其它視圖或布局中被渲染。這在子視圖中指定需要的 JavaScript 庫時(shí)非常有用:
@push('scripts') <script src="/example.js"> </script> @endpush
如果需要,可以多次壓入堆棧。通過向 @stack
指令傳遞堆棧名稱來完成堆棧內(nèi)容的渲染:
<head> <!-- 頭部內(nèi)容 --> @stack('scripts') </head>
如果想要將內(nèi)容預(yù)置在棧頂,需要使用 @prepend
指令:
@push('scripts') This will be second...@endpush// 然后... @prepend('scripts') This will be first... @endprepend
Service 注入
@inject
指令可以用于自 Laravel 的 服務(wù)容器 中獲取服務(wù)。傳遞給 @inject
的第一個(gè)參數(shù)是將要置入的服務(wù)變量名,第二個(gè)參數(shù)是希望被解析的類或接口名:
@inject('metrics', 'App\Services\MetricsService') <div> Monthly Revenue: {{ $metrics->monthlyRevenue() }}.</div>
擴(kuò)展 Blade
Blade 允許你使用 directive
方法自定義指令。當(dāng) Blade 編譯器遇到自定義指令時(shí),這會(huì)調(diào)用該指令包含的表達(dá)式提供的回調(diào)。
下面的例子創(chuàng)建了 @datetime($var)
指令,一個(gè)格式化給定的 DateTime
的實(shí)例 $var
:
<?php namespace App\Providers; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider{ /** * 執(zhí)行注冊(cè)后引導(dǎo)服務(wù). * * @return void */ public function boot() { Blade::directive('datetime', function ($expression) { return "<?php echo ($expression)->format('m/d/Y H:i'); ?>"; }); } /** * 在容器中注冊(cè)綁定. * * @return void */ public function register() { // } }
如你所見,我們將在傳遞給該指令的任意表達(dá)式中鏈?zhǔn)秸{(diào)用 format
方法。在這個(gè)例子中,該指令將生成如下原生 PHP 代碼:
<?php echo ($var)->format('m/d/Y H:i'); ?>
{note} 在更新 Blade 指令的邏輯之后,需要 Blade 視圖的所有緩存??梢允褂?
view:clear
Artisan 命令刪除 Blade 視圖緩存。
自定義 If 語句
在定義簡單的、自定義條件語句時(shí),編寫自定義指令比必須的步驟復(fù)雜。在這種情況下,Blade 提供了 Blade::if
方法,它允許你使用閉包快速度定義條件指令。例如,定義一個(gè)校驗(yàn)當(dāng)前應(yīng)用環(huán)境的自定義指令,可以在 AppServiceProvider
的 boot
方法中這樣做:
use Illuminate\Support\Facades\Blade; /** * 執(zhí)行注冊(cè)后引導(dǎo)服務(wù) * * @return void */ public function boot(){ Blade::if('env', function ($environment) { return app()->environment($environment); }); }
一旦定義了自定義條件指令,就可以在模板中輕松的使用:
@env('local') // 應(yīng)用在本地環(huán)境中運(yùn)行... @elseenv('testing') // 應(yīng)用在測(cè)試環(huán)境中運(yùn)行... @else // 應(yīng)用沒有在本地和測(cè)試環(huán)境中運(yùn)行... @endenv