[ Laravel 5.8 文件 ] 前端開發 —— Blade 模板引擎
簡介
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'); });
這樣在瀏覽器中訪問 http://blog.test/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