Laravel 文件閱讀:Blade 模版

zhangbao發表於2017-09-04

周星馳電影作品《功夫》截圖

簡介

Blade 是 Laravel 提供的模版引擎,它簡單、強大。不像其他的 PHP 模版引擎,Blade 允許在檢視中使用原生 PHP 程式碼。實際上,所有的 Blade 檢視最終都會被編譯為原生 PHP 程式碼,快取在 storage/framework/views 資料夾中。Laravel 使用的是這些編譯後的快取檔案,而不是檢視檔案本身,所以,Blade 對於應用程式來說幾乎是零開銷的。當你修改了檢視檔案,那麼它會重新編譯並快取,以便使用。Blade 檢視檔案以 .blade.php 作為字尾名,一般儲存在 resources/views 資料夾中。

模版繼承

定義佈局檔案

Blade 模版引擎的兩個主要優點是 模版繼承區塊。舉一個簡單的例子,一個專案裡,幾乎所有的頁面都是一樣的佈局,這時候就可以把這個佈局提煉出來,作為“母版頁”,繼承了這個母版頁的頁面都有一樣的佈局效果,稱為母版頁的“子頁”。母版頁還叫佈局檔案,佈局檔案就是一個 Blade 檢視:

<!-- Stored in 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 標籤,還用了兩個指令:@section@yield@section 指令定義區塊,@yield 指令定義區塊裡的內容。

下面,來定義佈局檔案的子頁。

繼承佈局檔案

子頁中,使用 Blade 的 @extends 指令指定“繼承”的佈局檔案,使用 @section 指令為在佈局檔案中使用 @section@yield 指令定義的地方注入內容。

<!-- Stored in resources/views/child.blade.php -->

@extends('layouts.app')

@section('title', 'Page Title')

@section('sidebar')

    <p>This is appended to the master sidebar.</p>
@endsection

@section('content')
    <p>This is my body content.</p>
@endsection

可以看到,在佈局檔案中使用 @yield 指令定義的地方,在子頁中仍然使用 @section 注入內容;在佈局檔案中使用 @section 指令定義的一個好處是:在子頁中使用 @section 注入內容時,可以使用 @ parent 指令附加(而非重寫)在佈局檔案中的內容,而在佈局檔案中使用 @yield 指令定義的地方是做不到的。 @ parent 指令會在檢視渲染時替換成佈局檔案裡的內容。

注意,與在佈局檔案裡定義的 sidebar 不同的是,子頁裡使用 @endsection 結束,而非 @show。因為 @endsection 僅用來定義區塊,而 @show 是用來定義、立馬產出區塊的。

從路由中返回檢視檔案,要用到全域性輔助函式 helper

Route::get('blade', function () {
    return view('child');
});

元件 & 插槽

元件和插槽提供了類似佈局和區塊的優點。而元件和插槽的心智模型更符合直覺。設想一下,在我們的專案中有一個可重用的“彈框”元件:

<!-- /resources/views/alert.blade.php -->

<div class="alert alert-danger">
    {{ $slot }}
</div>

{{ $slot }} 變數表示插入的元件內容。構建此元件,是使用 Blade 的 @component 指令。

@component('alert')
    <strong>Whoops!</strong> Something went wrong!
@endcomponent

在這個場景裡,{{ $slot }} 變數的內容就是

<strong>Whoops!</strong> Something went wrong!

有時一個元件需要有多個插槽。這時,只要稍許修改元件程式碼,定義一個“標題”插槽,這個插槽稱命名插槽。命名插槽是通過簡單地“列印”匹配其名稱的變數來顯示內容的:

<!-- /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

為元件傳遞額外資料

有時需要為元件傳遞額外資料。為此,可以為 @component 指令傳遞第二個陣列引數,指定要傳遞的額外資料。所有傳遞過去的額外資料作為變數,在元件模版裡都是可取的:

@component('alert', ['foo' => 'bar'])
    ...
@endcomponent

顯示資料

向 Blade 檢視傳遞資料,是通過將變數包裹在大括號([])裡實現的:

Route::get('greeting', function () {
    return view('welcome', ['name' => 'Samantha']);
});

下面,就可以用 name 變數顯示內容了:

Hello, {{ $name }}.

{{ }} 是 Blade 檢視的列印語句,當然,列印語句裡不限制只能列印變數內容,也可以使用 PHP 函式。實際上,列印語句這裡可以使用任何 PHP 程式碼:

The current UNIX timestamp is {{ time() }}.

顯示非轉義資料

預設,所有傳遞給 Blade {{ }} 語句的內容都會使用 htmlspecialchars 函式處理、將內容轉義,避免 XSS 攻擊。如果無需轉義輸出的內容,可以使用下面的語法:

Hello, {!! $name !!}.

不過千萬要小心,應該總是優先選擇使用轉義的 {{ }} 語法以避免 XSS 攻擊。因為,有時你很能難避免使用者有意的、無意的資料輸入。

Blade & JavaScript 框架

由於一些 JavaScript 框架也使用花括號({{ }})語法解析內容,為了區分開 Blade 和這些用到的 JavaScript 框架,你可以使用 @ 符號來告訴 Blade 模版渲染引擎說,這個地方不要動,保持原樣就可以了:

<h1>Laravel</h1>

Hello, @{{ name }}.

上面例子裡,@ 符號會從 Blade 中刪除,而 {{ name }} 會保持原樣,用來給你的 JavaScript 框架渲染使用。

@verbatim 指令

如果使用 JavaScript 框架渲染的模版區域很大,這時就可以用 @verbatim 指令包裹這些模版區域,這樣就避免了在每個 Blade 列印語句前都跟上 @ 符號的麻煩:

@verbatim
    <div class="container">
        Hello, {{ name }}.
    </div>
@endverbatim

控制結構

除了模版繼承和顯示資料,Blade 還為常見的 PHP 控制結構提供了快捷方式,比如條件判斷和迴圈。這些快捷方式提供了一種非常乾淨、簡潔的控制結構,並且保持了原生 PHP 的形式。

If 語句

構造 if 語句使用 @if@elseif@elseendif 指令。這些指令和原生 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($records)
    // $records is defined and is not null...
@endisset

@empty($records)
    // $records is "empty"...
@endempty

認證

@auth@guest 指令用來判斷當前使用者是認證使用者還是遊客:

@auth
    // The user is authenticated...
@endauth

@guest
    // 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

迴圈

Blade 還提供了迴圈方面的指令。再一次,這裡的每一個指令都與在原生 PHP 裡的對應功能相同。

@for ($i = 0; $i < 10; $i++)
    The current value is {{ $i }}
@endfor

[@foreach](https://learnku.com/users/5651) ($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

在迴圈時,也可以結束或者跳過當前的迭代:

[@foreach](https://learnku.com/users/5651) ($users as $user)
    @if ($user->type == 1)
        @continue
    @endif

    <li>{{ $user->name }}</li>

    @if ($user->number == 5)
        @break
    @endif
@endforeach

也可以在一行完成這些操作:

[@foreach](https://learnku.com/users/5651) ($users as $user)
    @continue($user->type == 1)

    <li>{{ $user->name }}</li>

    @break($user->number == 5)
@endforeach

$loop 變數

迴圈時,迴圈內部有一個可用的變數 $loop。這個變數提供了跟迴圈有關的有用資訊,比如當前迭代的索引、是否是第一次/最後依次迭代等:

[@foreach](https://learnku.com/users/5651) ($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 屬性父級迴圈裡的 $loop 變數:

[@foreach](https://learnku.com/users/5651) ($users as $user)
    [@foreach](https://learnku.com/users/5651) ($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 當前迴圈的巢狀水平(從 1 開始)。
$loop->parent 內層巢狀迴圈的父級 $loop 變數。

註釋

Blade 的註釋語法是 {{-- --}}。不像 HTMl 註釋,Blade 模版註釋不會出現在最終渲染的 HTML 程式碼裡:

{{-- This comment will not be present in the rendered HTML --}}

PHP

一些情況下,要在檢視裡嵌入 PHP 程式碼。這時可以用 [@php](https://learnku.com/users/10050) 指令在模版裡執行一塊原生 PHP 程式碼。

[@php](https://learnku.com/users/10050)
    //
@endphp

雖然 Blade 提供了此功能,但過於頻繁的使用它,可能說明你在模版裡嵌入太多邏輯程式碼了,這並不妥當。

引入子檢視

Blade 允許在一個檢視裡通過 @include 指令引入一個檢視。使用 @include 指令的檢視稱為父級檢視,引入的檢視稱為子檢視。父級檢視裡的所有變數在子檢視裡都是可以獲得的:

<div>
    @include('shared.errors')

    <form>
        <!-- Form Contents -->
    </form>
</div>

雖然子檢視會繼承父級檢視裡的所有變數,但是你也可以為引入的子檢視傳遞額外資料,這些資料是以陣列形式傳遞過去的:

@include('view.name', ['some' => 'data'])

當你用 @include 指令引入的檢視檔案不存在時,Laravel 會丟擲錯誤。如果引入的檢視檔案不確定是否存在,應該使用 @includeIf 指令:

@includeIf('view.name', ['some' => 'data'])

如果需要通過一個布林值判斷是否引入子檢視,需要使用 @includeWhen 指令:

@includeWhen($boolean, 'view.name', ['some' => 'data'])

注意,不要使用 __DIR____FILE__ 常量引入 Blade 檢視,因為程式實際使用的是編譯後的、快取在 storage/framework/views 資料夾中的檔案。

為集合渲染檢視

@each 指令整合了迴圈資料和引入檢視的功能:

@each('view.name', $jobs, 'job')

第一個引數是為陣列或者集合中每個元素渲染資料時指定的檢視,第二個引數是要遍歷的陣列或者集合,第三個引數是當前迭代的元素資料在子檢視裡的變數名。在上面的例子裡,我們遍歷的陣列是 jobs,在 view.name 檢視裡渲染 job 變數,當前迭代的 key 使用 key 變數獲取。

也可以為 @each 指令傳遞第四個引數,這是在給定陣列元素為空時渲染的檢視。

@each('view.name', $jobs, 'job', 'view.empty')

注意,使用 @each 指令渲染的檢視不從父級模版裡繼承變數。如果子檢視還需要這些變數,你應該使用 [@foreach](https://learnku.com/users/5651)@include 指令組合。

堆疊

Blade 允許你用 @push 指令向命名堆疊裡推入內容,命名堆疊以 @stack 指令定義,可以定義在普通檢視或者佈局檔案裡,推入的內容會在檢視或者佈局檔案裡渲染出來。這在,為子檢視新增額外的 JavaScript 庫的場景下,特別有用。

<!--  在檢視或者佈局檔案中定義堆疊 -->
<head>
    <!-- Head Contents -->

    @stack('scripts')
</head>

<!-- 在子檢視中推入堆疊內容 -->
@push('scripts')
    <script src="/example.js"></script>
@endpush

你可以盡你所需的多次向堆疊推入資料。

服務注入

@inject 指令可從 Laravel 的服務提供者中獲得服務。傳遞給 @inject 指令的第一個引數是一個變數名,獲得的服務就是存放在這個變數裡的,第二個引數就是你要解析的服務的類名或介面名。

@inject('metrics', 'App\Services\MetricsService')

<div>
    Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
</div>

擴充套件 Blade

Blade 允許你用 directive 方法建立自定義指令。當 Blade 解析遇到自定義指令時,會將接受到的表示式(expression)放在自定義指令的回撥閉包裡處理。

在下面的例子裡,我們為建立了一個 @datatime($var) 指令,$var 應該是一個 DateTime 例項:

<?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'); ?>";
        });
    }

    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

可以看到,我們在傳遞過來的表示式上使用了 format 方法。在這個例子裡,@datatime($var) 指令最終生成的 PHP 程式碼如下:

<?php echo ($var)->format('m/d/Y H:i'); ?>

注意,在更新自定義指令後,需要刪除所有的檢視快取檔案,可以用 view:cache Artisan 命令實現。

自定義 If 語句

當自定義指令涉及簡單的條件判斷時,使用 Blade::directive 的方式可能會變得稍複雜些。為此,Blade 引入了 Blade::if 方法,使用閉包來快速自定義條件判斷指令。例如,我們定義一個指令,判斷當前的專案環境,可以選擇在 AppServiceProviderboot 裡做這件事情:

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

是不是很簡單呢??

翻譯、衍生自:https://learnku.com/docs/laravel/5.5/blade

圖片來源:周星馳電影作品《功夫》。

相關文章