Blade 模板

简介

Blade 是 Laravel 提供的简单而强大的模板引擎。不像其它流行的 PHP 模板引擎,Blade 并不限制您在视图中使用原生 PHP 代码。实际上,所有 Blade 视图都会被编译成原生 PHP 代码并缓存起来,除非它们被修改,这意味着 Blade 基本上不会对应用产生任何开销。Blade 视图文件使用 .blade.php 文件扩展名并被存储在 resources/views 目录中。

模板继承

定义布局

使用 Blade 的两个主要好处是 _模板继承_ 和 _区块_。先让我们来看一个简单的示例。首先,我们将检查「主」页面布局。因为大多数应用在不同页面保持相同的整体布局,可以很方便地用单个 Blade 视图来定义该布局:

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 指令来指定子视图要「继承」的布局。继承 Blade 布局的视图可以使用 @section 指令将内容注入到布局的区块。请记住,如上述示例所示,可以在布局中使用 @yield 来显示这些区块的内容:

resources/views/child.blade.php

@extends('layouts.app')

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

@section('sidebar')
    @parent

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

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

在本例中,sidebar 区块利用 @parent 指令来追加(而不是覆盖)内容到布局的侧边栏。在视图渲染时,@parent 指令会被布局的内容替换。

和之前的例子相反,sidebar 区块以 @endsection 结束而不是 @show@endsection 指令只会定义一个区块,而 @show 会定义并 立即生成 该区块。

可以使用全局 view 辅助函数从路由中返回 Blade 视图:

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

组件 & 插槽

组件和插槽提供了与区块和布局类似的好处;但是,有些人可能会发现组件和插槽的思维模型更容易理解。首先,假设我们有一个在应用中多次使用的「警告框」组件:

resources/views/alert.blade.php

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

{{ $slot }} 变量将包含我们希望注入到组件的内容。现在,要构建此组件,我们可以使用 @component Blade 指令:

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

有时为组件定义多个插槽会很有帮助。让我们修改警告框组件来允许注入一个「标题」。命名的插槽会显示与它们的名字相匹配变量的「输出」:

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 组件存储在子目录中,可能希望为它们添加别名来便于访问。例如,假设一个 Blade 组件存储在 resources/views/components/alert.blade.php。可以使用 component 方法将 components.alert 化名为 alert。通常,应该在 AppServiceProviderboot 方法中进行此操作:

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

显示数据

可以将变量包裹在花括号中来显示传递给 Blade 视图的数据。例如,给定如下路由:

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

可以像这样显示 name 变量的内容:

Hello, {{ $name }}.

当然,不仅限于显示传递给视图的变量内容。还可以输出任何 PHP 函数的结果。实际上,可以在 Blade 输出语句中使用任何 PHP 代码:

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

Blade 的 {{ }} 语句会自动使用 PHP 的 htmlspecialchars 函数来防止 XSS 攻击。

显示未转义数据

默认情况下,Blade 的 {{ }} 语句会自动使用 PHP 的 htmlspecialchars 函数来防止 XSS 攻击。如果不想数据被转义,可以使用如下语法:

Hello, {!! $name !!}.

在输出用户提供的内容时要非常小心。显示用户提供的内容时,应始终使用转义的双花括号语法来防止 XSS 攻击。

渲染 JSON

有时为了渲染 JSON 数据并用于初始化一个 JavaScript 变量,可能会将数组传递给视图。例如:

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

除了手动调用 json_encode 外,还可以使用Blade 指令 @json

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

HTML 字符实体编码

默认情况下,Blade(以及 Laravel e 辅助函数)会对 HTML 字符实体进行双重编码。如果要禁用双重编码,可以在 AppServiceProviderboot 方法中调用 Blade::withoutDoubleEncoding 方法:

namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 启动任何应用服务
     *
     * @return void
     */
    public function boot()
    {
        Blade::withoutDoubleEncoding();
    }
}

Blade & JavaScript 框架

由于许多 JavaScript 框架也是用「花括号」语法来指示浏览器显示指定的表达式,可以使用 @ 符号来通知 Blade 渲染引擎应该保持表达式不变。例如:

<h1>Laravel</h1>

Hello, @{{ name }}.

在本示例中,@ 将被 Blade 移除;但是,{{ name }} 表达式会被 Blade 引擎保持不变,来允许它由 JavaScript 框架渲染。

@verbatim 指令

如果要在模板的大块内容中显示 JavaScript 变量,可以使用 @verbatim 指令将 HTML 包裹起来,这样就不必在每个 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

除了已经讨论过的条件指令之外,@isset@empty 指令可以用作对应 PHP 函数的快捷操作:

@isset($records)
    // $records 定义了并且不为 null
@endisset

@empty($records)
    // $records 为「空」
@endempty

认证指令

@auth@guest 指令用于判断当前用户是否已认证或是访客:

@auth
    // 用户已认证
@endauth

@guest
    // 用户没有认证
@endguest

如果需要,使用 @auth@guest 指令时还可以指定要检查的 认证看守器

@auth('admin')
    // 用户已认证
@endauth

@guest('admin')
    // 用户没有认证
@endguest

区块指令

可以使用 @hasSection 指令检查区块是否有内容:

@hasSection('navigation')
    <div class="pull-right">
        @yield('navigation')
    </div>

    <div class="clearfix"></div>
@endif

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

当循环时,可以使用 循环变量 来获取循环的有用信息,例如是否处于循环的第一次或最后一次迭代。

使用循环时也可以终止或跳过当前迭代。

@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 代码嵌入到视图中很有用。可以使用 Blade 的 @php 指令在模板中执行一段原生的 PHP:

@php
    //
@endphp

尽管 Blade 提供了该功能,然而频繁使用它就表明在模板中嵌入了太多逻辑。

表单

CSRF 字段

无论何时,在应用中定义一个 HTML 表单时,都应在表单中包含一个隐藏的 CSRF 令牌字段,以便 CSRF 保护 中间件可以验证该请求。可以使用 @csrf Blade 指令生成令牌字段:

<form method="POST" action="/profile">
    @csrf

    ...
</form>

请求方法字段

由于 HTML 表单不能发送 PUTPATCHDELETE 请求,需要添加一个隐藏的 _method 字段来伪造这些 HTTP 请求类型。@method Blade 指令可用来创建该字段:

<form action="/foo/bar" method="POST">
    @method('PUT')

    ...
</form>

引入子视图

Blade 的 @include 指令可以在另一个视图中引入一个 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'])

应避免在 Blade 视图中使用 __DIR____FILE__ 常量,因为它们会引用编译并缓存后的视图位置。

为集合渲染视图

可以使用 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>
    <!-- 头部内容 -->

    @stack('scripts')
</head>

如果要将内容添加到视图栈的开始,应该使用 @prepend 指令:

@push('scripts')
    This will be second...
@endpush

// 然后

@prepend('scripts')
    This will be first...
@endprepend

服务注入

@inject 指令可用于从 Laravel 的 服务容器 获取服务。第一个传递给 @inject 的参数是服务会被放入的变量名,而第二个参数是希望解析的服务的类名或接口名:

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

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

扩展 Blade

Blade 允许您使用 directive 方法来定义自己的指令。当 Blade 编译器遇到自定义指令时,会调用该指令包含的表达式提供的回调。

以下示例会创建一个 @datetime($var) 指令来格式化给定的 $var,此值应该是一个 DateTime 的实例:

namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 启动注册的服务
     *
     * @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('m/d/Y H:i'); ?>

更新 Blade 指令的逻辑后,需要删除所有缓存的 Blade 视图。可以使用 Artisan 命令 view:clear 来移除所有缓存的 Blade 视图。

自定义 If 语句

在定义简单的自定义条件语句时,编写自定义指令有时会更复杂。因此,Blade 提供了 Blade::if 方法允许您使用闭包快速定义自定义指令。例如,让我们来定义一个检查当前应用环境的自定义条件。我们可以在 AppServiceProviderboot 方法中进行此操作:

use Illuminate\Support\Facades\Blade;

/**
 * 启动注册的服务
 *
 * @return void
 */
public function boot()
{
    Blade::if('env', function ($environment) {
        return app()->environment($environment);
    });
}

定义之后,就可以在模板中轻松使用它:

@env('local')
    // 应用在本地环境
@elseenv('testing')
    // 应用在测试环境
@else
    // 应用不在本地环境或测试环境
@endenv