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
。通常,应该在 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
显示数据
可以将变量包裹在花括号中来显示传递给 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 字符实体进行双重编码。如果要禁用双重编码,可以在 AppServiceProvider
的 boot
方法中调用 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 表单不能发送 PUT
,PATCH
或 DELETE
请求,需要添加一个隐藏的 _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
方法允许您使用闭包快速定义自定义指令。例如,让我们来定义一个检查当前应用环境的自定义条件。我们可以在 AppServiceProvider
的 boot
方法中进行此操作:
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