Blade 模板
介绍
Blade 是 Laravel 提供的简单而强大的模板引擎。与其他流行的 PHP 模板引擎不同,Blade 不限制您在视图中使用纯 PHP 代码。实际上,所有 Blade 视图都被编译成纯 PHP 代码并缓存,直到它们被修改,这意味着 Blade 对您的应用程序几乎没有额外的开销。Blade 视图文件使用 .blade.php
文件扩展名,通常存储在 resources/views
目录中。
模板继承
定义布局
使用 Blade 的两个主要好处是 模板继承 和 部分。首先,让我们看一个简单的例子。首先,我们将检查一个“主”页面布局。由于大多数 Web 应用程序在各个页面之间保持相同的总体布局,因此将此布局定义为单个 Blade 视图是很方便的:
<!-- 存储在 resources/views/layouts/app.blade.php -->
<html>
<head>
<title>应用名称 - @yield('title')</title>
</head>
<body>
@section('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', '页面标题')
@section('sidebar')
@@parent
<p>这是附加到主侧边栏的内容。</p>
@endsection
@section('content')
<p>这是我的正文内容。</p>
@endsection
在此示例中,sidebar
部分使用 @@parent
指令将内容附加(而不是覆盖)到布局的侧边栏。视图渲染时,@@parent
指令将被布局的内容替换。
与前面的示例相反,此 sidebar
部分以 @endsection
结束,而不是 @show
。@endsection
指令只会定义一个部分,而 @show
会定义并立即显示该部分。
@yield
指令还接受默认值作为其第二个参数。如果未定义要显示的部分,则将呈现此值:
@yield('content', View::make('view.name'))
可以使用全局 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>哎呀!</strong> 出了点问题!
@endcomponent
要指示 Laravel 从给定的可能视图数组中加载第一个存在的视图作为组件,您可以使用 componentFirst
指令:
@componentfirst(['custom.alert', 'alert'])
<strong>哎呀!</strong> 出了点问题!
@endcomponentfirst
有时定义组件的多个插槽是有帮助的。让我们修改我们的警报组件以允许注入“标题”。可以通过“回显”与其名称匹配的变量来显示命名插槽:
<!-- /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')
禁止
@endslot
您无权访问此资源!
@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'])
您无权访问此资源!
@endalert
如果组件没有其他插槽,您可以省略组件参数:
@alert
您无权访问此资源!
@endalert
显示数据
您可以通过将变量包裹在大括号中来显示传递给 Blade 视图的数据。例如,给定以下路由:
Route::get('greeting', function () {
return view('welcome', ['name' => 'Samantha']);
});
您可以像这样显示 name
变量的内容:
Hello, {{ $name }}.
Blade {{ }}
语句会自动通过 PHP 的 htmlspecialchars
函数以防止 XSS 攻击。
您不仅限于显示传递给视图的变量的内容。您还可以回显任何 PHP 函数的结果。实际上,您可以在 Blade 回显语句中放置任何您希望的 PHP 代码:
当前的 UNIX 时间戳是 {{ time() }}。
显示未转义的数据
默认情况下,Blade {{ }}
语句会自动通过 PHP 的 htmlspecialchars
函数以防止 XSS 攻击。如果您不希望数据被转义,可以使用以下语法:
Hello, {!! $name !!}.
在回显由应用程序用户提供的内容时要非常小心。显示用户提供的数据时,请始终使用转义的双大括号语法以防止 XSS 攻击。
渲染 JSON
有时您可能会将数组传递给视图,目的是将其渲染为 JSON 以初始化 JavaScript 变量。例如:
<script>
var app = <?php echo json_encode($array); ?>;
</script>
然而,您可以使用 @json
Blade 指令,而不是手动调用 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 实体进行双重编码。如果您希望禁用双重编码,请从 AppServiceProvider
的 boot
方法中调用 Blade::withoutDoubleEncoding
方法:
<?php
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 }}
表达式将保持不变,由您的 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)
我有一条记录!
@elseif (count($records) > 1)
我有多条记录!
@else
我没有任何记录!
@endif
为了方便起见,Blade 还提供了一个 @unless
指令:
@unless (Auth::check())
您未登录。
@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)
第一种情况...
@break
@case(2)
第二种情况...
@break
@default
默认情况...
@endswitch
循环
除了条件语句,Blade 还为处理 PHP 的循环结构提供了简单的指令。同样,这些指令的功能与其 PHP 对应物完全相同:
@for ($i = 0; $i < 10; $i++)
当前值是 {{ $i }}
@endfor
@foreach ($users as $user)
<p>这是用户 {{ $user->id }}</p>
@endforeach
@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>没有用户</p>
@endforelse
@while (true)
<p>我将永远循环。</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)
这是第一次迭代。
@endif
@if ($loop->last)
这是最后一次迭代。
@endif
<p>这是用户 {{ $user->id }}</p>
@endforeach
如果您在嵌套循环中,您可以通过 parent
属性访问父循环的 $loop
变量:
@foreach ($users as $user)
@foreach ($user->posts as $post)
@if ($loop->parent->first)
这是父循环的第一次迭代。
@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 中:
{{-- 此注释不会出现在渲染的 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>
验证错误
@error
指令可用于快速检查给定属性是否存在验证错误消息。在 @error
指令中,您可以回显 $message
变量以显示错误消息:
<!-- /resources/views/post/create.blade.php -->
<label for="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">电子邮件地址</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>
</div>
即使包含的视图将继承父视图中可用的所有数据,您也可以将额外数据数组传递给包含的视图:
@include('view.name', ['some' => 'data'])
如果您尝试 @include
一个不存在的视图,Laravel 将抛出错误。如果您希望包含一个可能存在也可能不存在的视图,您应该使用 @includeIf
指令:
@includeIf('view.name', ['some' => 'data'])
如果您希望在给定布尔表达式计算为 true
时包含视图,可以使用 @includeWhen
指令:
@includeWhen($boolean, 'view.name', ['some' => 'data'])
如果您希望在给定布尔表达式计算为 false
时包含视图,可以使用 @includeUnless
指令:
@includeUnless($boolean, 'view.name', ['some' => 'data'])
要从给定视图数组中包含第一个存在的视图,您可以使用 includeFirst
指令:
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])
您应该避免在 Blade 视图中使用 __DIR__
和 __FILE__
常量,因为它们将引用缓存的、已编译视图的位置。
别名包含
如果您的 Blade 包含存储在子目录中,您可能希望为它们设置别名以便于访问。例如,假设一个 Blade 包含存储在 resources/views/includes/input.blade.php
,内容如下:
<input type="{{ $type ?? 'text' }}">
您可以使用 include
方法将包含从 includes.input
别名为 input
。通常,这应该在 AppServiceProvider
的 boot
方法中完成:
use Illuminate\Support\Facades\Blade;
Blade::include('includes.input', 'input');
一旦包含被别名,您可以使用别名名称作为 Blade 指令渲染它:
@input(['type' => 'email'])
为集合渲染视图
您可以将循环和包含结合到一行中,使用 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')
这将是第二个...
@endpush
// 稍后...
@prepend('scripts')
这将是第一个...
@endprepend
服务注入
@inject
指令可用于从 Laravel 服务容器中检索服务。传递给 @inject
的第一个参数是服务将放入的变量的名称,而第二个参数是您希望解析的服务的类或接口名称:
@inject('metrics', 'App\Services\MetricsService')
<div>
月收入:{{ $metrics->monthlyRevenue() }}。
</div>
扩展 Blade
Blade 允许您使用 directive
方法定义自己的自定义指令。当 Blade 编译器遇到自定义指令时,它将调用提供的回调,并包含指令包含的表达式。
以下示例创建了一个 @datetime($var)
指令,该指令格式化给定的 $var
,该 $var
应为 DateTime
实例:
<?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
方法,允许您使用闭包快速定义自定义条件指令。例如,让我们定义一个检查当前应用程序环境的自定义条件。我们可以在 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
@unlessenv('production')
// 应用程序不在生产环境中...
@endenv