簡介
Blade 是由 Laravel 提供的非常簡單但功能強大的模板引擎,不同於其他流行的 PHP 模板引擎,Blade 在視圖中並不約束你使用 PHP 原生代碼。所有的 Blade 視圖最終都會被編譯成原生 PHP 代碼並緩存起來直到被修改,這意味着對應用的性能而言 Blade 基本上是零開銷。Blade 視圖文件使用 .blade.php
文件擴展並存放在 resources/views
目錄下。
模板繼承
定義布局
使用 Blade 的兩個最大優點是模板繼承和片段組合,開始之前讓我們先看一個例子。首先,我們測試“主”頁面布局,由於大多數 Web 應用在不同頁面中使用同一個布局,可以很方便的將這個布局定義為一個單獨的 Blade 頁面:
<!-- 存放在 resources/views/layouts/app.blade.php --> <html> <head> <title>應用名稱 - @yield('title')</title> </head> <body> @section('sidebar') 這里是側邊欄 @show <div class="container"> @yield('content') </div> </body> </html>
正如你所看到的,該文件包含典型的 HTML 標記,不過,注意 @section
和 @yield
指令,前者正如其名字所暗示的,定義了一個內容片段,而后者用於顯示給定片段的內容。
現在我們已經為應用定義了一個布局,接下來讓我們定義繼承該布局的子頁面吧。
繼承布局
定義子頁面的時候,可以使用 Blade 的 @extends
指令來指定子頁面所繼承的布局,繼承一個 Blade 布局的視圖可以使用 @section
指令注入內容到布局定義的內容片段中,記住,如上面例子所示,這些片段的內容將會顯示在布局中使用 @yield
的地方:
<!-- 存放在 resources/views/child.blade.php --> @extends('layouts.app') @section('title', 'Laravel學院') @section('sidebar') @parent <p>Laravel學院致力於提供優質Laravel中文學習資源</p> @endsection @section('content') <p>這里是主體內容,完善中...</p> @endsection
在本例中,sidebar
片段使用 @parent
指令來追加(而非覆蓋)內容到繼承布局的側邊欄,@parent
指令在視圖渲染時將會被布局中的內容替換。
注:與之前的示例相反,
sidebar
部分以@endsection
結束而不是@show
,@endsection
指令只是定義一個 section 而@show
指令定義並立即返回這個 section。
Blade 視圖可以通過 view
方法直接從路由中返回:
Route::get('blade', function () { return view('child'); });
這樣在瀏覽器中訪問 /blade
,就可以看到頁面顯示如下:
現在頁面還很粗糙,沒有任何樣式,后面學習前端組件后可以回來完善。
組件 & 插槽
組件和插槽給內容片段(section)和布局(layout)帶來了方便,不過,有些人可能會發現組件和插槽的模型更容易理解。首先,我們假設有一個可復用的“alert”組件,我們想要在整個應用中都可以復用它:
<!-- /resources/views/alert.blade.php --> <div class="alert alert-danger"> {{ $slot }} </div>
{{ $slot }}
變量包含了我們想要注入組件的內容,現在,要構建這個組件,我們可以使用 Blade 指令 @component
:
@component('alert') <strong>Whoops!</strong> Something went wrong! @endcomponent
有時候為組件定義多個插槽很有用。下面我們來編輯alert組件以便可以注入“標題”,命名插槽可以通過“echoing”與它們的名字相匹配的變量來顯示:
<!-- /resources/views/alert.blade.php --> <div class="alert alert-danger"> <div class="alert-title">{{ $title }}</div> {{ $slot }} </div>
現在,我們可以使用指令 @slot
注入內容到命名的插槽。任何不在 @slot
指令中的內容都會被傳遞到組件的 $slot
變量中:
@component('alert') @slot('title') Forbidden @endslot You are not allowed to access this resource! @endcomponent
當我們在瀏覽器中查看這個組件內容的話,對應輸出如下:
這段代碼的意思是通過組件名 alert
去查找對應的視圖文件,裝載到當前視圖,然后通過組件中 @slot
定義的插槽內容去渲染插槽視圖中對應的插槽位,如果組件沒有為某個插槽位定義對應的插槽內容片段,則組件中的其他不在 @slot
片段中的內容將會用於渲染該插槽位,如果沒有其他多余內容則對應插槽位為空。
傳遞額外數據到組件
有時候你可能需要傳遞額外數據到組件,出於這個原因,你可以傳遞數組數據作為第二個參數到 @component
指令,所有數據都會在組件模板中以變量方式生效:
@component('alert', ['foo' => 'bar']) ... @endcomponent
組件別名
如果 Blade 組件存儲在子目錄中,你可能想要給它們起別名以便訪問。例如,假設有一個存放在 resources/views/components/alert.blade.php
的 Blade 組件,你可以使用 component
方法將這個組件設置別名為 alert
(原名是 components.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
如果沒有額外插槽的話也可以省略組件參數:
@alert You are not allowed to access this resource! @endalert
數據顯示
可以通過兩個花括號包裹變量來顯示傳遞到視圖的數據,比如,如果給出如下路由:
Route::get('greeting', function () { return view('welcome', ['name' => '學院君']); });
那么可以通過如下方式顯示 name
變量的內容:
你好, {{ $name }}。
注:Blade 的
{{}}
語句已經經過 PHP 的htmlentities
函數處理以避免 XSS 攻擊。
當然,不限制顯示到視圖中的變量內容,你還可以輸出任何 PHP 函數的結果,實際上,可以將任何 PHP 代碼放到 Blade 模板語句中:
The current UNIX timestamp is {{ time() }}.
顯示原生數據
默認情況下,Blade 的 {{ }}
語句已經通過 PHP 的 htmlentities
函數處理以避免 XSS 攻擊,如果你不想要數據被處理,比如要輸出帶 HTML 元素的富文本,可以使用如下語法:
Hello, {!! $name !!}.
注:輸出用戶提供的內容時要當心,對用戶提供的內容總是要使用雙花括號包裹以避免 XSS 攻擊。
渲染 JSON 內容
有時候你可能會將數據以數組方式傳遞到視圖再將其轉化為 JSON 格式以便初始化某個 JavaScript 變量,例如:
<script> var app = <?php echo json_encode($array); ?>; </script>
這樣顯得很麻煩,有更簡便的方式來實現這個功能,那就是 Blade 的 @json
指令:
<script> var app = @json($array); </script>
HTML 實體編碼
默認情況下,Blade(以及輔助函數 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. * * @return void */ public function boot() { Blade::withoutDoubleEncoding(); } }
Blade & JavaScript 框架
由於很多 JavaScript 框架也是用花括號來表示要顯示在瀏覽器中的表達式,如 Vue,我們可以使用 @
符號來告訴 Blade 渲染引擎該表達式應該保持原生格式不作改動。比如:
<h1>Laravel</h1>
Hello, @{{ name }}.
在本例中,@
符在編譯階段會被 Blade 移除,但是,{{ name }}
表達式將會保持不變,從而可以被 JavaScript 框架正常渲染。
@verbatim
指令
如果你在模板中有很大一部分篇幅顯示 JavaScript 變量,那么可以將這部分 HTML 封裝在 @verbatim
指令中,這樣就不需要在每個 Blade 輸出表達式前加上 @
前綴:
@verbatim <div class="container"> Hello, {{ name }}. </div> @endverbatim
流程控制
除了模板繼承和數據顯示之外,Blade 還為常用的 PHP 流程控制提供了便利操作,例如條件語句和循環,這些快捷操作提供了一個干凈、簡單的方式來處理 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
此外,Blade 還提供了 @isset
和 @empty
指令,分別對應 PHP 的 isset
和 empty
方法:
@isset($records) // $records is defined and is not null... @endisset @empty($records) // $records is "empty"... @endempty
認證指令
@auth
和 @guest
指令可用於快速判斷當前用戶是否登錄:
@auth // 用戶已登錄... @endauth @guest // 用戶未登錄... @endguest
如果需要的話,你也可以在使用 @auth
和 @guest
的時候指定 認證guard(詳細請閱讀安全認證那篇文章) :
@auth('admin') // The user is authenticated... @endauth @guest('admin') // The user is not authenticated... @endguest
Section 指令
你可以使用 @hasSection
指令判斷某個 section 中是否有內容:
@hasSection('navigation') <div class="pull-right"> @yield('navigation') </div> <div class="clearfix"></div> @endif
Switch 語句
switch
語句可以通過 @switch
,@case
,@break
,@default
和 @enswitch
指令構建:
@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
注:在循環的時候可以使用
$loop
變量獲取循環信息,例如是否是循環的第一個或最后一個迭代。
使用循環的時候還可以結束循環或跳出當前迭代:
@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
$loop
變量
在循環的時候,可以在循環體中使用 $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 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->depth |
當前循環的嵌套層級 |
$loop->parent |
嵌套循環中的父級循環變量 |
注釋
Blade 還允許你在視圖中定義注釋,然而,不同於 HTML 注釋,Blade 注釋並不會包含到 HTML 中被返回:
{{-- This comment will not be present in the rendered HTML --}}
PHP
在一些場景中,嵌入 PHP 代碼到視圖中很有用,你可以使用 @php
指令在模板中執行一段原生 PHP 代碼:
@php // @endphp
注:盡管 Blade 提供了這個特性,如果過於頻繁地使用它意味着你在視圖模板中嵌入了過多的業務邏輯,需要注意。
表單
CSRF 字段
任何時候你想要在應用中定義 HTML 表單,都需要在表單中引入隱藏的 CSRF 令牌字段,以便 CSRF保護中間件可以通過請求驗證。你可以使用 Blade 指令 @csrf
來生成令牌字段:
<form method="POST" action="/profile">
@csrf
...
</form>
方法字段
由於 HTML 表單不能發起 PUT
、PATCH
以及 DELETE
請求,你需要添加隱藏的 _method
字段來偽造這些 HTTP 請求。Blade 指令 @method
可用於創建這個字段:
<form action="/foo/bar" method="POST"> @method('PUT') ... </form>
包含子視圖
Blade 的 @include
指令允許你很輕松地在一個視圖中包含另一個 Blade 視圖,所有父級視圖中變量在被包含的子視圖中依然有效:
<div> @include('shared.errors') <form> <!-- Form Contents --> </form> </div>
上述指令會在當前目錄下的 shared
子目錄中尋找 errors.blade.php
文件並將其內容引入當前視圖。
盡管被包含的視圖可以繼承所有父視圖中的數據,你還可以傳遞額外參數到被包含的視圖:
@include('view.name', ['some' => 'data'])
當然,如果你嘗試包含一個不存在的視圖,Laravel 會拋出錯誤,如果你想要包含一個有可能不存在的視圖,可以使用 @includeIf
指令:
@includeIf('view.name', ['some' => 'data'])
如果包含的視圖取決於一個給定的布爾條件,可以使用 @includeWhen
指令:
@includeWhen($boolean, 'view.name', ['some' => 'data'])
要包含給定數組中的第一個視圖,可以使用 @includeFirst
指令:
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])
注:不要在 Blade 視圖中使用
__DIR__
和__FILE__
常量,因為它們會指向緩存視圖的路徑。
@include
和 @component
有什么區別,兩者有共同之處,都用於將其他內容引入當前視圖,我理解的區別在於 @include
用於粗粒度的視圖包含,@component
用於細粒度的組件引入,@component
通過插槽機制對引入視圖內容可以進行更加細粒度的控制,如果你只是引入一塊視圖內容片段,用 @include
即可,如果想要在當前視圖對引入視圖內容片段進行調整和控制,則可以考慮使用 @component
。
為子視圖引入設置別名
如果引入的子視圖存放在某個子目錄中,你可能希望為它們設置別名以便於訪問。例如,假設 Blade 子視圖存放在 resources/views/includes/input.blade.php
中,對應內容如下:
<input type="{{ $type ?? 'text' }}">
你可以使用 include
方法來為這個引入設置別名,將 includes.input
設置為 input
,通常,該操作在 AppServiceProvider
的 boot
方法中完成:
use Illuminate\Support\Facades\Blade; Blade::include('includes.input', 'input');
這樣一來,就可以將設置的別名作為 Blade 指令用來渲染子視圖了:
@input(['type' => 'email'])
在視圖中渲染集合數據
你可以使用 Blade 的 @each
指令通過一行代碼循環引入多個局部視圖:
@each('view.name', $jobs, 'job')
該指令的第一個參數是數組或集合中每個元素要渲染的局部視圖,第二個參數是你希望迭代的數組或集合,第三個參數是要分配給當前視圖的變量名。舉個例子,如果你要迭代一個 jobs
數組,通常你需要在局部視圖中訪問 $job
變量。在局部視圖中可以通過 key
變量訪問當前迭代的鍵。
你還可以傳遞第四個參數到 @each
指令,該參數用於指定給定數組為空時渲染的視圖:
@each('view.name', $jobs, 'job', 'view.empty')
注:通過
@each
渲染的視圖不會從父視圖中繼承變量,如果子視圖需要這個變量,可以使用@foreach
和@include
指令來替代。
堆棧
Blade 允許你推送內容到命名堆棧,以便在其他視圖或布局中渲染。這在子視圖中引入指定 JavaScript 庫時很有用:
@push('scripts') <script src="/example.js"></script> @endpush
推送次數不限,要渲染完整的堆棧內容,傳遞堆棧名稱到 @stack
指令即可:
<head> <!-- Head Contents --> @stack('scripts') </head>
如果你想要將內容顯示在堆棧開頭,需要使用 @prepend
指令:
@push('scripts') This will be second... @endpush // Later... @prepend('scripts') This will be first... @endprepend
服務注入
@inject
指令可以用於從服務容器中獲取服務,傳遞給 @inject
的第一個參數是服務對應的變量名,第二個參數是要解析的服務類名或接口名:
@inject('metrics', 'App\Services\MetricsService') <div> Monthly Revenue: {{ $metrics->monthlyRevenue() }}. </div>
擴展 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 { /** * Perform post-registration booting of services. * * @return void */ public function boot() { \Blade::directive('datetime', function($expression) { return "<?php echo ($expression)->format('m/d/Y H:i'); ?>"; }); } /** * 在容器中注冊綁定. * * @return void */ public function register() { // } }
正如你所看到的,我們可以將 format
方法應用到任何傳入指令的表達式上,所以,在本例中,該指令最終生成的 PHP 代碼如下:
<?php echo ($var)->format('Y/m/d H:i'); ?>
注:更新完 Blade 指令邏輯后,必須刪除所有的 Blade 緩存視圖。緩存的 Blade 視圖可以通過 Artisan 命令
view:clear
移除。
自定義 If 語句
在定義一些簡單、自定義的條件語句時,編寫自定義指令往往復雜性大於必要性,因為這個原因,Blade 提供了一個 Blade::if
方法通過閉包的方式快速定義自定義的條件指令,例如,我們來自定義一個條件來檢查當前應用的環境,我們可以在 AppServiceProvider
的 boot
方法中定義這段邏輯:
use Illuminate\Support\Facades\Blade; /** * Perform post-registration booting of services. * * @return void */ public function boot() { \Blade::if('env', function ($environment) { return app()->environment($environment); }); }
定義好自定義條件后,就可以在模板中使用了:
@env('local') // The application is in the local environment... @elseenv('testing') // The application is in the testing environment... @else // The application is not in the local or testing environment... @endenv