Laravel 視圖渲染:Blade 模板引擎
由 學院君 創建于3年前, 最后更新于 2年前
版本號 #1
53378 views
27 likes
0 collects
Blade 簡介
Blade 是由 Laravel 提供的非常簡單但功能強大的模板引擎,不同于其他流行的 PHP 模板引擎,Blade 在視圖中并不約束你使用 PHP 原生代碼。所有的 Blade 視圖最終都會被編譯成原生 PHP 代碼并緩存起來直到被修改,這意味著對應用的性能而言 Blade 基本上是零開銷。Blade 視圖文件使用 .blade.php 文件擴展并存放在 resources/views 目錄下。
模板繼承
定義布局
使用 Blade 的兩個最大優點是模板繼承和片段組合,開始之前讓我們先看一個例子。首先,我們測試“主”頁面布局,由于大多數 Web 應用在不同頁面中使用同一個布局,可以很方便的將這個布局定義為一個單獨的 Blade 頁面:
應用名稱 - @yield('title')@section('sidebar')
這里是側邊欄
@show
@yield('content')
正如你所看到的,該文件包含典型的 HTML 標記,不過,注意 @section 和 @yield 指令,前者正如其名字所暗示的,定義了一個內容片段,而后者用于顯示給定片段的內容。
現在我們已經為應用定義了一個布局,接下來讓我們定義繼承該布局的子頁面吧。
繼承布局
定義子頁面的時候,可以使用 Blade 的 @extends 指令來指定子頁面所繼承的布局,繼承一個 Blade 布局的視圖可以使用 @section 指令注入內容到布局定義的內容片段中,記住,如上面例子所示,這些片段的內容將會顯示在布局中使用 @yield 的地方:
@extends('layouts.app')
@section('title', 'Laravel學院')
@section('sidebar')
@parent
Laravel學院致力于提供優質Laravel中文學習資源
@endsection
@section('content')
這里是主體內容,完善中...
@endsection
在本例中,sidebar 片段使用 @parent 指令來追加(而非覆蓋)內容到繼承布局的側邊欄,@parent 指令在視圖渲染時將會被布局中的內容替換。
當然,和原生 PHP 視圖一樣,Blade 視圖可以通過 view 方法直接從路由中返回:
Route::get('blade', function () {
return view('child');
});
這樣在瀏覽器中訪問 http://blog.dev/blade,就可以看到頁面顯示如下:

現在頁面還很粗糙,沒有任何樣式,后面學習前端組件后可以回來完善。
組件&插槽
組件和插槽給內容片段(section)和布局(layout)帶來了方便,不過,有些人可能會發現組件和插槽的模型更容易理解。首先,我們假設有一個可復用的“alert”組件,我們想要在整個應用中都可以復用它:
{{ $slot }}
{{ $slot }} 變量包含了我們想要注入組件的內容,現在,要構建這個組件,我們可以使用 Blade 指令 @component:
@component('alert')
Whoops! Something went wrong!
@endcomponent
有時候為組件定義多個插槽很有用。下面我們來編輯alert組件以便可以注入“標題”,命名插槽可以通過“echoing”與它們的名字相匹配的變量來顯示:
{{ $slot }}
現在,我們可以使用指令 @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
數據顯示
可以通過兩個花括號包裹變量來顯示傳遞到視圖的數據,比如,如果給出如下路由:
Route::get('greeting', function () {
return view('welcome', ['name' => '學院君']);
});
那么可以通過如下方式顯示 name 變量的內容:
你好, {{ $name }}。
當然,不限制顯示到視圖中的變量內容,你還可以輸出任何 PHP 函數的結果,實際上,可以將任何 PHP 代碼放到 Blade 模板語句中:
The current UNIX timestamp is {{ time() }}.
注:Blade 的 {{}} 語句已經經過 PHP 的 htmlentities 函數處理以避免 XSS 攻擊。
輸出存在的數據
有時候你想要輸出一個變量,但是不確定該變量是否被設置,我們可以通過如下 PHP 代碼:
{{ isset($name) ? $name : 'Default' }}
除了使用三元運算符,Blade 還提供了更簡單的方式:
{{ $name or 'Default' }}
在本例中,如果 $name 變量存在,其值將會顯示,否則將會顯示 Default。
顯示原生數據
默認情況下,Blade 的 {{ }} 語句已經通過 PHP 的 htmlentities 函數處理以避免 XSS 攻擊,如果你不想要數據被處理,比如要輸出帶 HTML 元素的富文本,可以使用如下語法:
Hello, {!! $name !!}.
注:輸出用戶提供的內容時要當心,對用戶提供的內容總是要使用雙花括號包裹以避免直接輸出 HTML 代碼。
渲染 JSON 內容
有時候你可能會將數據以數組方式傳遞到視圖再將其轉化為 JSON 格式以便初始化某個 JavaScript 變量,例如:
var app = <?php echo json_encode($array); ?>;
這樣顯得很麻煩,有更簡便的方式來實現這個功能,那就是 Blade 的 @json 指令:
var app = @json($array);
Blade & JavaScript 框架
由于很多 JavaScript 框架也是用花括號來表示要顯示在瀏覽器中的表達式,如 Vue,我們可以使用 @ 符號來告訴 Blade 渲染引擎該表達式應該保持原生格式不作改動。比如:
Laravel
Hello, @{{ name }}.
在本例中,@ 符在編譯階段會被 Blade 移除,但是,{{ name }} 表達式將會保持不變,從而可以被 JavaScript 框架正常渲染。
@verbatim指令
如果你在模板中有很大一部分篇幅顯示 JavaScript 變量,那么可以將這部分 HTML 封裝在 @verbatim 指令中,這樣就不需要在每個 Blade 輸出表達式前加上 @ 前綴:
@verbatim
Hello, {{ name }}.
@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 的時候指定登錄用戶類型:
@auth('admin')
// The user is authenticated...
@endauth
@guest('admin')
// The user is not authenticated...
@endguest
關于用戶登錄認證我們后面再講到用戶認證的時候再深入探討。
Switch 語句
switch 語句可以通過 @switch,@case,@break,@default 和 @endswitch 指令構建:
@switch($i)
@case(1)
First case...
@break
@case(2)
Second case...
@break
@default
Default case...
@endswitch
和 PHP 中的 switch 語句結構完全一致。
循環
除了條件語句,Blade 還提供了簡單的指令用于處理 PHP 的循環結構,同樣,這些指令的功能和 PHP 對應功能完全一樣:
@for ($i = 0; $i < 10; $i++)
The current value is {{ $i }}
@endfor
@foreach ($users as $user)
This is user {{ $user->id }}
@endforeach
@forelse ($users as $user)
{{ $user->name }}@empty
No users
@endforelse
@while (true)
I'm looping forever.
@endwhile
注:在循環的時候可以使用 $loop 變量獲取循環信息,例如是否是循環的第一個或最后一個迭代。
使用循環的時候還可以結束循環或跳出當前迭代:
@foreach ($users as $user)
@if ($user->type == 1)
@continue
@endif
{{ $user->name }}@if ($user->number == 5)
@break
@endif
@endforeach
還可以使用指令聲明來引入條件:
@foreach ($users as $user)
@continue($user->type == 1)
{{ $user->name }}@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
This is user {{ $user->id }}
@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 提供了這個特性,如果過于頻繁地使用它意味著你在視圖模板中嵌入了過多的業務邏輯,需要注意。
包含子視圖
Blade 的 @include 指令允許你很輕松地在一個視圖中包含另一個 Blade 視圖,所有父級視圖中變量在被包含的子視圖中依然有效:
@include('shared.errors')
上述指令會在當前目錄下的 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 的 @each 指令通過一行代碼循環引入多個局部視圖:
@each('view.name', $jobs, 'job')
該指令的第一個參數是數組或集合中每個元素要渲染的局部視圖,第二個參數是你希望迭代的數組或集合,第三個參數是要分配給當前視圖的變量名。舉個例子,如果你要迭代一個 jobs 數組,通常你需要在局部視圖中訪問 $job 變量。在局部視圖中可以通過 key 變量訪問當前迭代的鍵。
你還可以傳遞第四個參數到 @each 指令,該參數用于指定給定數組為空時渲染的視圖:
@each('view.name', $jobs, 'job', 'view.empty')
堆棧
Blade 允許你推送內容到命名堆棧,以便在其他視圖或布局中渲染。這在子視圖中引入指定 JavaScript 庫時很有用:
@push('scripts')
@endpush
推送次數不限,要渲染完整的堆棧內容,傳遞堆棧名稱到 @stack 指令即可:
@stack('scripts')
服務注入
@inject 指令可以用于從服務容器中獲取服務,傳遞給 @inject 的第一個參數是服務對應的變量名,第二個參數是要解析的服務類名或接口名:
@inject('metrics', 'App\Services\MetricsService')
Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
擴展 Blade
Blade 甚至還允許你自定義指令,可以使用 directive 方法來注冊一個指令。當 Blade 編譯器遇到該指令,將會傳入參數并調用提供的回調。
下面的例子創建了一個 @datetime($var) 指令格式化給定的 DateTime 的實例 $var:
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 date('Y-m-d H:i:s', $expression); ?>";
});
}
/**
* 在容器中注冊綁定.
*
* @return void
*/
public function register()
{
//
}
}
正如你所看到的,我們可以將 datetime 方法應用到任何傳入指令的表達式上:
@datetime(1508888888)
最終該指令生成的 PHP 代碼如下:
注:更新完 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...
@else
The application is not in the local environment...
@endenv