Blade Templates

简介

Blade 是 Laravel 提供的一个简单而又强大的模板引擎。和其他流行的 PHP 模板引擎不同,Blade 并不限制你在视图中使用原生 PHP 代码。所有 Blade 视图文件都将被编译成原生的 PHP 代码并缓存起来,除非它被修改,否则不会重新编译,这就意味着 Blade 基本上不会给你的应用增加任何负担。Blade 视图文件使用 .blade.php作为文件扩展名,被存放在 resources/views 目录。

模板继承

定义布局

Blade 的两个主要优点是 模板继承区块。为方便入门,让我们先通过一个简单的例子来上手。首先,我们来研究一个「主」页面布局。因为大多数 web 应用会在不同的页面中使用相同的布局方式,因此可以很方便地定义单个 Blade 布局视图:

  1. <!-- 保存在 resources/views/layouts/app.blade.php 文件中 -->
  2. <html>
  3. <head>
  4. <title>App Name - @yield('title')</title>
  5. </head>
  6. <body>
  7. @section('sidebar')
  8. This is the master sidebar.
  9. @show
  10. <div class="container">
  11. @yield('content')
  12. </div>
  13. </body>
  14. </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 指令向布局的 sidebar 追加(而非覆盖)内容。 在渲染视图时,@parent 指令将被布局中的内容替换。

Tip:和上一个示例相反,这里的 sidebar 片段使用 @endsection 代替 @show 来结尾。 @endsection 指令仅定义了一个片段, @show 则在定义的同时 立即 yield 这个片段。

@yield 指令还接受一个默认值作为第二个参数。如果被 「yield」的片段未定义,则该默认值被渲染:

@yield('content', View::make('view.name'))

Blade 视图可以使用全局 view 助手自路由中返回:

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

组件 & 插槽

组件和插槽提供了与片段和布局类似的好处;不过组件和插槽的思维模型更易于理解。我们先来看一个可复用的「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

若要指示Laravel从组件的给定数组中加载存在的第一个视图, 可以使用 componentFirst 指令:

@componentFirst(['custom.alert', 'alert'])
    <strong>Whoops!</strong> Something went wrong!
@endcomponent

有时候为一个组件定义多个插槽是很有用的。修改 alert 组件以允许其注入 「title」。命名插槽可以通过与其匹配的 「回显」 变量显示:

<!-- /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 组件存储在 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 }}.

Tip:Blade {{ }} 语句是自动经过 PHP 的 htmlspecialchars函数传递来防范 XSS 攻击的。

不限于显示传递给视图的变量的内容,你还可以显示任一 PHP 函数的结果。实际上,你可以在Blade 的回显语句中放置你想要的任意 PHP 代码:

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

显示非转义字符

默认情况下, Blade 中 {{ }} 语句自动经由 PHP 的 htmlspecialchars函数传递以防范 XSS 攻击。如果不希望数据被转义,可以使用下面的语法:

Hello, {!! $name !!}.

注意:在回显应用的用户提供的内容时需要谨慎小心。在显示用户提供的数据时,有必要一直使用双花括号语法转义来防范 XSS 攻击。

渲染 JSON

有时,为了初始化一个JavaScript变量,你可能会向视图传递一个数组,并将其渲染成JSON。例如:

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

然而,你需要使用@jsonBlade指令,而不是手动调用 json_encode@json 指令和PHP的json_encode函数具有相同的参数。

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

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

{注意} 你应该只使用@json指令去将现有的变量渲染为JSON。Blade模板基于正则表达式,并且尝试将复杂表达式传递给指令这可能会导致意外故障。

@json 指令还可用于植入Vue组件或 data-*属性:

<example-component :some-prop='@json($array)'></example-component>

{注意} 在元素属性中使用@json需要用单引号括起来。

HTML 实体编码

默认情况下,Blade(和Laravel的 e助手函数)将对HTML实体进行双重编码。如果你想要禁用双重编码,请从AppServiceProviderboot方法中调用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 框架也使用“大括号”来表示预设的表达式应该显示在浏览器中,因此可以使用 @ 符号通知Blade渲染引擎表达式应保持不变。例如:

<h1>Laravel</h1>

Hello, @{{ name }}.

在这个例子中, @ 符号将被 Blade 删除;然而,在 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

除了已经讨论过的条件指令之外, @isset@empty 指令还可以用作各自PHP函数的快捷方式:

@isset($records)
    // $records is defined and is not null...
@endisset

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

身份验证指令

@auth@guest 指令可用于快速确定当前用户是否经过身份验证或是guest:

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

@guest
    // The user is not authenticated...
@endguest

如果需要,可以在使用 @auth@guest 指令时指定应被校验的 身份验证

@auth('admin')
    // The user is authenticated...
@endauth

@guest('admin')
    // The user is not authenticated...
@endguest

片段指令

你可以使用 @hasSection 指令检查片段是否包含指定内容:

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

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

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循环结构的简单指令。同样,这些指令的每个函数都与它们相对于的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->even是否是循环中的偶数迭代。
$loop->odd是否是循环中的奇数迭代。
$loop->depth当前迭代的嵌套深度级数。
$loop->parent嵌套循环中,父循环的循环变量。

注释

Blade 也允许在视图中定义注释。不过与 HTML 注释不同,Blade 注释不会包含在返回给应用的 HTML 中:

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

PHP

某些情况下,在视图中嵌入 PHP 代码很有用。可以在模板中使用 @php 指令执行原生的 PHP 代码块:

@php
    //
@endphp

Tip:尽管 Blade 提供了这个特性,但频繁使用意味着模板中嵌入了过多的逻辑。

表单

CSRF 域

只要在应用中定义了 HTML 表单,就一定要在表单中包含隐藏的 CSRF 令牌域,这样一来 CSRF 保护 中间件就能校验请求。可以使用 Blade 的 @csrf 指令生成令牌域:

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

    ...
</form>

Method 域

HTML 表单不能发出 PUTPATCHDELETE 请求,需要加入隐藏的 _method 域来模仿这些 HTTP 动词。Blade 的 @method 指令能够帮你创建这个域:

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

    ...
</form>

验证错误

@error 指令可以快速检查 验证错误消息 这个属性是否存在. 在 @error 指令中, 你可以使用 $message 变量来显示验证错误内容:

<!-- /resources/views/post/create.blade.php -->

<label for="title">Post Title</label>

<input id="title" type="text" class="@error('title') is-invalid @enderror">

@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror

你也可以指定错误验证字段名字 作为第二个参数传递给 @error 指令去获取验证错误内容并可以通过多种形式展示在页面上:

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

<label for="email">Email address</label>

<input id="email" type="email" class="@error('email', 'login') is-invalid @enderror">

@error('email', 'login')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror

引入子视图

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'])

想要包含一个依赖于给定布尔条件的视图,可以使用 @includeWhen 指令:

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

要包含给定视图数组中第一个存在的视图,可以使用 includeFirst 指令:

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

注意:应当尽量避免在 Blade 视图中使用 DIRFILE 魔术常量,因为它们将指向缓存中经过编译的视图的位置。

给被包含的视图起别名

如果你的 Blade 被包含视图们存储在子目录中,你可能会希望为它们起个易于访问的别名。例如,一个带有如下内容的 Blade 视图内容被存储在 resources/views/includes/input.blade.php 文件中:

<input type="{{ $type ?? 'text' }}">

可以使用 include 方法为 includes.input 起一个叫做 input 的别名。通常,这会在 AppServiceProviderboot 方法中完成:

use Illuminate\Support\Facades\Blade;

Blade::include('includes.input', 'input');

一旦被包含的视图拥有了别名,就可以像 Blade 指令一样使用别名渲染它:

@input(['type' => 'email'])

为集合渲染视图

可以使用 Blade 的 @each 指令在一行中整合循环和包含:

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

第一个参数是渲染数组或集合的每个元素的视图片段。第二个参数是希望被迭代的数组或集合,第三个参数则是将被分配给视图中当前迭代的变量名。例如,想要迭代 jobs 数组,通常会在视图片段中使用 job 变量访问每个任务。当前迭代的 key 将作为视图片段中的 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

Service 注入

@inject 指令可以用于自 Laravel 的 服务容器 中获取服务。传递给 @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
{
    /**
     * 在容器中注册绑定.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * 执行注册后引导服务.
     *
     * @return void
     */
    public function boot()
    {
        Blade::directive('datetime', function ($expression) {
            return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
        });
    }
}

如你所见,我们将在传递给该指令的任意表达式中链式调用 format 方法。在这个例子中,该指令将生成如下原生 PHP 代码:

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

注意:在更新Blade 指令的逻辑之后,需要删除 Blade 视图的所有缓存。可以使用 view:clear Artisan 命令删除 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

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接 我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。