展開文件目錄

Blade 模板

介紹

Blade 是 Laravel 所提供的簡單且強大的模板引擎。相較於其它知名的 PHP 模板引擎,Blade 並不會限制你必須在視圖中使用 PHP 程式碼。所有 Blade 視圖會被編譯成一般的 PHP 程式碼並快取直到它們被更動為止,這代表著基本上 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 指令被用來顯示給定區塊的內容。

現在,我們已經定義了這個應用程式的佈局,讓我們來定義一個繼承此佈局的子頁面。

繼承頁面框架

定義子視圖時,請使用 @extends 指令告訴子視圖應該要「繼承」哪個佈局。而繼承 Blade 佈局的視圖可以使用 @section 指令將內容注入佈局中。提醒一下,上面範例出現的 @yield 會用來顯示子視圖使用 @section 裡的內容:

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

@extends('layouts.app')

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

@section('sidebar')
    @parent

    <p>這裡放置側欄</p>
@endsection

@section('content')
    <p>在這裡放主要內容</p>
@endsection

在這個範例中,sidebar 區塊利用了 @parent 指令增加(而不是覆蓋)內容至佈局的側邊欄。@parent 指令會在視圖輸出時被置換成佈局的內容。

{tip} 有一點要澄清一下,這邊的 sidebar 結束時是使用 @endsection 而不是 @show@endsection 指令只會定義一個區塊,而 @show直接產生這個區塊的內容。

你可以在路由使用全域的 view 輔助函式來取得 Blade 視圖:

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

元件 & Slots

元件與 slots 提供類似佈局的 @section 的用法,然後你會發現這種用法的可讀性很高。首先,讓我們設想一個可以在整個應用程式中被重複使用的「提醒」元件:

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

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

{{ $slot }} 變數會把我們希望放入的內容注入到元件中。這時候,我們能使用 Blade 中的 @component 指令來建構這個元件。

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

有時候元件定義多個 slots 是很有幫助的。讓我們修改一下剛剛的寫的「提醒元件」,讓他能再注入一個「title」。可以通過簡單地「呼叫」他們對應的變數名稱讓內容顯示在 @slots 區塊內:

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

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

    {{ $slot }}
</div>

現在,我們終於能使用 @slot 指令注入內容到對應變數名稱的 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 }}.

當然,也不是一定只能顯示傳遞至視圖的變數內容。你也可以顯示 PHP 函式的結果。實際上,你可以放置任何你需要的 PHP 程式碼到 Blade 顯示語法裡面:

目前的 UNIX 時間戳記為 {{ time() }}.

{tip} Blade 的 {{}} 語法已經自動以 PHP 的 htmlentites 函式防禦 XSS 攻擊。

顯示未跳脫的資料

在預設下,Blade {{ }} 語法會自己使用 PHP htmlspecialchars 原生函式來避免 XSS 攻擊。如果你不想要你的資料被跳脫處理,可以使用下面的語法。

Hello, {!! $name !!}.

{note} 當你的應用程式列印資料時,要特別小心由使用者提供的內容,最好使用跳脫的雙大括號語法來防止 XSS 攻擊。

渲染 JSON

有時候能會傳送一組陣列到你的視圖,目的是將陣列渲染成 JSON 來初始化 JavaScript 變數,例如:

<script>
    var app = <?php echo json_encode($array); ?>;
</script>

你可以使用 @json Blade 指令:

<script>
    var app = @json($array);
</script>

Blade & JavaScript 框架

由於許多 JavaScript 框架也使用「大」括號在瀏覽器中顯示給定的表達式,你可以使用 @ 符號來告知 Blade 渲染引擎該表達式應該維持原樣。舉個例子:

<h1>Laravel</h1>

Hello, @{{ name }}.

在這個範例中,@ 符號會被 Blade 移除。而且,Blade 引擎會保留 {{ name }} 表達式,如此一來便可讓其它 JavaScript 框架所應用。

@verbatim 指令

如果你的模板大部分要用來顯示 JavaScript 變數,你可以把 HTML 語法內容放到 @verbatim ,如此一來你就不用在每個 Blade echo 語句前加上 @ 符號:

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

控制結構

除了模板繼承與顯示資料功能以外,Blade 也提供了方便的縮寫給一般的 PHP 控制敘述,像是條件陳述式和迴圈。這些縮寫提供了乾淨、簡潔的方式來使用 PHP 的控制結構,同時還保留對應在 PHP 中熟悉且同樣的語法。

If 陳述式

你可以使用 @if@elseif@else@endif 指令建構 if 陳述式。這些指令的功能等同於在 PHP 中的語法:

@if (count($records) === 1)
    我有一條紀錄!
@elseif (count($records) > 1)
    我有多條紀錄!
@else
    我沒有任何記錄!
@endif

為了方便,Blade 也提供了 @unless 指令:

@unless (Auth::check())
    You are not signed in.
@endunless

除了上面已經討論過的指令外,@isset@empty 對於 PHP 原生函式提供更優雅的方法:

@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')
    // 使用者已經被認證...
@endauth

@guest('admin')
    // 使用者尚未被認證...
@endguest

Switch 陳述式

可以使用 @switch@case@break@default@endswitch 來建構 Switch 語法:

@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

{tip} 正在執行迴圈時,你可以使用迴圈變數來取得迴圈的訊息。例如你想知道目前是在迴圈的第一次還是最後一次。

正在使用迴圈的時候,你也可以終止或略過當前的迭代:

@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。這個變數提供存取一些有用的資訊,像是目前迴圈索引以及當前迴圈是第一次還是最後一次迭代:

@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

如果你使用一個巢狀迴圈,你可以透過 parent 屬性去存取 $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 當前迭代次數的索引(從 0 開始
$loop->iteration 當前迴圈的迭代(從 1 開始)
$loop->remaining 迴圈剩餘的迭代
$loop->count 計算迭代中的陣列項目總數
$loop->first 判斷是否是第一次迭代
$loop->last 判斷是否是最後一次迭代
$loop->depth 當前迴圈的巢狀級別
$loop->parent 在巢狀迴全中,使用父迴圈的變數

註解

Blade 允許你在你的視圖中寫註解。然而,這不像 HTML 的註解。Blade 註解不是寫入 HTML 並返回到你的網頁上:

{{-- 這裡的註解不會出現再渲染後的 HTML --}}

PHP

在一些情況下,你會想在視圖上直接寫 PHP 程式碼。你能使用 @php 這個Blade 指令在你的模板中執行 PHP :

@php
    //
@endphp

{tip} 雖然 Blade 提供這個功能,但過度使用會使你的應用程式變的不優雅唷!

引入子視圖

@include 這個 Blade 指令可以讓你引入其他 Blade 的視圖,主要視圖使用到的變數可與子視圖共用:

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

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

儘管被引入的視圖會繼承父視圖中的所有資料,你也可以傳遞額外資料的陣列至被引入的頁面:

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

當然,如果你嘗試 @include 一個不存在的視圖, Laravel 會拋出錯誤訊息。如果你想引入一個不一定存在的視圖,你應當使用 @includeIf

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

如果你想要根據布林值來決定是否要 @include 視圖內容,你可以使用 @includeWhen

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

如果你想要從已存在的視圖陣列中引入第一個視圖,你可以使用 @includeFirst

@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])

{note} 你應該避免在 Blade 視圖中使用 __DIR____FILE__ 常數,因為他們會引用視圖被的快取位置。

為集合渲染視圖

你可以使用 Blade 的 @each 指令將迴圈及引入結合成一行:

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

第一個參數為對陣列或集合的每個元素渲染的局部視圖。第二個參數為你要迭代的陣列或集合,而第三個參數為迭代時被分配至視圖中的變數名稱。所以,舉例來說,如果你迭代一個 jobs 陣列,通常你會希望在局部視圖中透過 job 變數存取每一個 job。目前迭代的 key 在你的視圖部份將會被作為 key 變數。

你也可以傳遞第四個參數至 @each 指令。此參數為當給定的陣列為空時,將會被渲染的視圖。

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

{note} 視圖透過 @each 渲染的視圖不會繼承父視圖的變數。如果子視圖需要這些變數,你應該改用 @foreach@include

Stacks

Blade 可以讓你使用 @push 將已命名的 Stack 在其他的視圖或佈局中渲染,這樣能更優雅的在子視圖中指定需要的 JavaScript 程式庫:

@push('scripts')
    <script src="/example.js"></script>
@endpush

你可以根據需求推送多個 @stack。你只需要將 Stack 名稱傳給 @stack,就能來渲染完整 stack 內容:

<head>
    <!-- Head Contents -->

    @stack('scripts')
</head>

服務注入

@inject 指令可以取出 Laravel 服務容器中的服務。傳遞給 @inject 的第一個參數為置放該服務的變數名稱,而第二個參數為你想要解析的服務的類別或是介面的名稱:

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

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

擴充 Blade

Blade 甚至可以讓你使用 directive 方法來自訂想要的指令。當 Blade 編譯器遇到自訂的指令時,它會呼叫所有註冊的指令提供的回呼函式。

以下範例建立一個 @datetime($var) Blade 指令,用於格式化給定的$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 方法到任何你想要傳入指令的表達式。所以,在這個範例的指令最後產生的 PHP 會是:

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

{note} 在更新 Blade 指令的邏輯後,你會需要刪除全部 Blade 視圖的快取。被快取的 Blade 視圖可以使用 Artisan 的 view:clear 指令來移除。

自訂 If 陳述句

在定義簡單的條件陳述句時,有時候會比編譯自訂的 Blade 指令更複雜。是因為 Blade 提供了 Blade::if 方法,它允許你使用閉包快速自訂 Blade 條件陳述句指令。例如,讓我們定義一個自訂的條件句來檢查應用程式當下的環境,我們可以在 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);
    });
}

一旦自訂了 Blade 條件句,我們就能在模板上輕易的使用它們:

@env('local')
    // 應用程式在本機環境中...
@elseenv('testing')
    // 應用程式在測試環境...
@else
    // 應用程式都不在應用程式與本機環境...
@endenv