# Blade 模板
- [简介](#introduction)
- [模板继承](#template-inheritance)
- [定义布局](#defining-a-layout)
- [继承布局](#extending-a-layout)
- [显示数据](#displaying-data)
- [Blade & JavaScript 框架](#blade-and-javascript-frameworks)
- [流程控制](#control-structures)
- [If 语句](#if-statements)
- [Switch 语句](#switch-statements)
- [循环](#loops)
- [循环遍历](#the-loop-variable)
- [注释](#comments)
- [PHP](#php)
- [表单](#forms)
- [CSRF 字段](#csrf-field)
- [Method 字段](#method-field)
- [验证错误](#validation-errors)
- [组件](#components)
- [显示组件](#displaying-components)
- [组件传参](#passing-data-to-components)
- [管理属性](#managing-attributes)
- [插槽](#slots)
- [内联组件视图](#inline-component-views)
- [匿名组件](#anonymous-components)
- [引入子视图](#including-subviews)
- [为集合渲染视图](#rendering-views-for-collections)
- [堆栈](#stacks)
- [服务注入](#service-injection)
- [Blade 扩展](#extending-blade)
- [自定义 If 语句](#custom-if-statements)
## 简介
Blade 是 Laravel 提供的一个简单而又强大的模板引擎。和其他流行的 PHP 模板引擎不同,Blade 并不限制你在视图中使用原生 PHP 代码。所有 Blade 视图文件都将被编译成原生的 PHP 代码并缓存起来,除非它被修改,否则不会重新编译,这就意味着 Blade 基本上不会给你的应用增加任何负担。Blade 视图文件使用 `.blade.php`作为文件扩展名,被存放在 `resources/views` 目录。
## 模板继承
### 定义布局
Blade 的两个主要优点是 `模板继承`和`区块 `。为方便入门,让我们先通过一个简单的例子来上手。首先,我们来研究一个「主」页面布局。因为大多数 web 应用会在不同的页面中使用相同的布局方式,因此可以很方便地定义单个 Blade 布局视图:
App Name - @yield('title')
@section('sidebar')
This is the master sidebar.
@show
@yield('content')
如你所见,该文件包含了典型的 HTML 语法。不过,请注意 `@section` 和 `@yield` 指令。 `@section` 指令定义了视图的一部分内容,而 `@yield` 指令是用来显示指定部分的内容。
现在,我们已经定义好了这个应用程序的布局,接下来,我们定义一个继承此布局的子页面。
### 继承布局
在定义一个子视图时,使用 Blade 的 `@extends` 指令指定子视图要「继承」的视图。扩展自 Blade 布局的视图可以使用 `@section` 指令向布局片段注入内容。就如前面的示例中所示,这些片段的内容将由布局中的 `@yield` 指令控制显示:
@extends('layouts.app')
@section('title', 'Page Title')
@section('sidebar')
@parent
This is appended to the master sidebar.
@endsection
@section('content')
This is my body content.
@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');
});
## 显示数据
可以将变量放在大括号中传递到 `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 !!}.
> {note} 在显示用户提供的内容时需要谨慎小心。在显示用户提供的数据时,有必要一直使用双花括号语法转义来防范 XSS 攻击。
#### 渲染 JSON
有时,为了初始化一个 `JavaScript` 变量,你可能会向视图传递一个数组,并将其渲染成 JSON。例如:
然而,你可以使用 `@json` Blade 指令来代替 `json_encode`,而不是手动调用 `json_encode`。`@json` 指令和 `PHP` 的 `json_encode` 函数拥有相同的参数。
> {note} 使用 `@json` 指令时应将已存在的变量渲染为 JSON。 Blade 模板是基于正则表达式处理的,如果将复杂的表达式传递给 `@json` 是有可能出错的。
`@json` 指令还可用于植入 Vue 组件或 `data-*` 属性:
> {note} 在 HTML 标签的属性中使用 `@json` 指令时需要用单引号。
#### HTML 实体编码
默认情况下,Blade (和 Laravel 的 `e` 助手函数) 将对 HTML 实体进行双重编码。 如果你想要禁用双重编码,请从 `AppServiceProvider` 的 `boot` 方法中调用中 `Blade::withoutDoubleEncoding`方法:
### Blade & JavaScript 框架
由于许多 `JavaScript` 框架也使用 “大括号” 来表示预设的表达式应该显示在浏览器中,因此可以使用 @ 符号表示 Blade 渲染引擎表达式应保持不变。例如:
Laravel
Hello, @{{ name }}.
在这个例子中, `@` 符号将被 Blade 删除;在 Blade 引擎中 `{{ name }}` 表达式将保持不变,取而代之的是 `JavaScript` 引擎来渲染该表达式。
#### `@verbatim` 指令
如果你在模板中显示了很大一部分的 `JavaScript` 变量,可以将 `HTML` 套嵌在 `@verbatim` 指令中,这样就不必在每个 `Blade` 回显语句前面加上 `@` 符号:
@verbatim
Hello, {{ name }}.
@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 已定义且不为空...
@endisset
@empty($records)
// 变量 $records 为空...
@endempty
#### 认证指令
`@auth` 和 `@guest` 指令可用于快速确定当前用户已通过身份验证或者是游客身份:
@auth
// 用户身份已被验证…
@endauth
@guest
// 用户身份未被验证…
@endguest
如果需要,可以在使用 `@auth` 和 `@guest` 指令时检查 [身份验证保护](/docs/{{version}}/authentication) :
@auth('admin')
// 用户身份已被验证…
@endauth
@guest('admin')
// 用户身份未被验证…
@endguest
#### 片段指令
您可以使用 `@hasSection` 指令检查片段是否包含指定内容:
@hasSection('navigation')
@yield('navigation')
@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)
This is user {{ $user->id }}
@endforeach
@forelse ($users as $user)
{{ $user->name }}
@empty
No users
@endforelse
@while (true)
I'm looping forever.
@endwhile
> {tip} 循环时,您可以使用 [loop](#the-loop-variable) 获取有关循环的有价值的信息,例如您是在循环的第一个迭代还是最后一个迭代。
使用循环时,您也可以结束循环或跳过当前迭代:
@foreach ($users as $user)
@if ($user->type == 1)
@continue
@endif
{{ $user->name }}
@if ($user->number == 5)
@break
@endif
@endforeach
您还可以在指令声明中包含条件:
@foreach ($users as $user)
@continue($user->type == 1)
{{ $user->name }}
@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
This is user {{ $user->id }}
@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 保护](https://learnku.com/docs/laravel/laravel/6.x/csrf) 中间件就能校验请求。可以使用 Blade 的 `@csrf` 指令生成令牌域:
### Method 域
HTML 表单不能发出 `PUT`、 `PATCH` 及 `DELETE` 请求,需要加入隐藏的 `_method` 域来模拟这些 HTTP 动作。Blade 的 `@method` 指令能够帮你创建这个域:
### 错误验证
`@error` 指令可以快速检查 [验证错误消息](https://learnku.com/docs/laravel/7.x/validation#quick-displaying-the-validation-errors) 这个属性是否存在。在 `@error` 指令中,您可以回显该 `$message` 变量以显示错误消息:
Post Title
@error('title')
{{ $message }}
@enderror
您也可以指定错误验证字段名字,作为第二个参数传递给 `@error` 指令去获取验证错误内容,并通过多种方式展示在页面上:
Email address
@error('email', 'login')
{{ $message }}
@enderror
## 组件
组件和插槽的作用与片段(Section)和布局(Layout)类似。不过,有些人可能认为组件和插槽使用起来更加方便。Laravel 支持两种编写组件的方法:基于类的组件和匿名组件。
我们可以用 Artisan 命令 `make:component` 来创建一个基于类的组件(组件类)。以下将通过创建一个简单的 `Alert` 组件来向你说明如何使用组件。使用 `make:component` 命令创建的组件将位于 `app/View/Components` 目录中。
php artisan make:component Alert
`make:component` 命令同时也自动为该组件建立了一个视图模板。该视图位于 `resources/views/components` 目录中
#### 手动注册包组件
在默认情况下,Laravel 将会在 `app/View/Components` 和 `resources/views/components` 自动发现并注册组件。
不过,如果你的组件使用了 Blade 组件,则需要手动注册组件类并为其注册 HTML 标签别名。你可以在服务提供者(Service Provider)中的 `boot` 方法处注册组件:
use Illuminate\Support\Facades\Blade;
/**
* Bootstrap your package's services.
*/
public function boot()
{
Blade::component(AlertComponent::class, 'package-alert');
}
注册组件后,你将可以通过 HTML 标签别名来使用它:
### 显示组件
你可以在任一 Blade 模板中使用 Blade 组件标签来显示组件。Blade 组件标签以 `x-` 开头,后面接上组件类的 kebab case 名称(即单词与单词间使用连字符 `-`):
如果组件类位于 `app/View/Components` 目录中的子目录中,则可以使用 `.` 字符指定目录层级。例如,某组件位于 `app/View/Components/Inputs/Button.php`,则可以这样渲染:
### 组件传参
你可以使用 HTML 属性将数据传递给 Blade 组件。普通的值可以通过简单的 HTML 属性传递,而 PHP 表达式及变量应当通过以 `:` 为前缀的属性传递:
你可以在组件类的构造函数中定义组件所需的数据。组件类中的所有公共属性都将自动传递给组件视图。不必通过组件类的 `render` 方法传递:
type = $type;
$this->message = $message;
}
/**
* 获取组件的视图 / 内容
*
* @return \Illuminate\View\View|string
*/
public function render()
{
return view('components.alert');
}
}
渲染组件时,可以通过变量名称来获取组件类公共属性的内容:
{{ $message }}
#### 组件方法
除了可获取组件类的公共属性外,还可以在组件视图中执行组件类上的任何公共方法。例如,某组件具有一个 `isSelected` 方法:
/**
* 判断给定选项是否为当前选项
*
* @param string $option
* @return bool
*/
public function isSelected($option)
{
return $option === $this->selected;
}
你可以通过调用与方法名称相同的变量来执行该方法:
{{ $label }}
#### 附加依赖项
如果你的组件需要使用 Laravel 的 [服务容器](/docs/{{version}}/container) 中的依赖项,则应当在组件所有数据属性之前列出它们,它们将会被容器自动注入:
use App\AlertCreator
/**
* 创建组件实例
*
* @param \App\AlertCreator $creator
* @param string $type
* @param string $message
* @return void
*/
public function __construct(AlertCreator $creator, $type, $message)
{
$this->creator = $creator;
$this->type = $type;
$this->message = $message;
}
### 管理属性
我们已经了解了如何将数据属性传递给组件。然而,有时候我们可能需要指定其他的 HTML 属性(如 `class`),这些属性不是组件所需要的数据。这种情况下,我们将会想要将这些属性向下传递到组件模板的根元素。例如,我们要渲染一个 `alert` 组件,如下所示:
所有不属于组件构造函数的属性都将自动添加到组件的「属性包」中。该属性包将会通过 `$attributes` 变量传递给组件视图。通过输出此变量,即可在组件中呈现所有属性:
#### 默认 / 合并属性
某些时候,你可能需要指定属性的默认值,或将其他值合并到组件的某些属性中。为此,你可以使用属性包的 `merge` 方法:
merge(['class' => 'alert alert-'.$type]) }}>
{{ $message }}
假设我们如下方所示使用该组件:
最终呈现的组件 HTML 将如下所示:
### 插槽
通常,你需要通过 `slots` 向组件传递附加内容。 假设我们创建的 `alert` 组件具有以下标记:
{{ $slot }}
我门可以通过向组件注入内容的方式,将内容传递到 `slots` :
Whoops! Something went wrong!
有时候一个组件可能需要在它内部的不同位置放置多个不同的插槽。我们来修改一下alert组件,使其允许注入 `title` 。
{{ $title }}
{{ $slot }}
你可以使用 `x-slot` 标签来定义一个命名插槽的内容。而不在 `x-slot` 标签中的其它内容都将传递给 `$slot` 变量中的组件:
Server Error
Whoops! Something went wrong!
### 内联组件视图
对于非常小型的组件,管理组件类和视图模版的操作可能会显得过于复杂。因此,你可以直接通过 `render` 方法返回组件的内容:
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\View\View|string
*/
public function render()
{
return <<<'blade'
{{ $slot }}
blade;
}
#### 创建内联视图组件
如果需要创建一个能够渲染内联视图的组件,你可以在执行 `make:component` 命令时使用 `inline` 选项:
php artisan make:component Alert --inline
### 匿名组件
与内联组件类似,匿名组件也提供了通过单个文件管理组件的机制。然后,匿名组件使用的是一个没有关联类的单个视图文件。要定义匿名组件,只需在 `resources/views/components` 目录下创建一个Blade模版文件。例如,假设 你在 `resources/view/components/alert.blade.php` 中定义了一个组件:
你可以使用 `.` 字符来标明组件是否在 `components` 的深层目录中。比如,假设组件是定义在 `resources/views/components/inputs/button.blade.php` ,你可以像下面这样来渲染它:
#### 数据 / 属性
由于匿名组件没有任何关联的类,您可能想知道如何区分哪些数据应该作为变量传递给组件,而哪些属性应该放在组件的[属性包](管理属性)中。
你可以使用组件Blade模板顶部的 `@props` 指令来指定哪些属性应该视为数据变量。而组件上的所有其他属性将通过组件的属性包进行提供:
@props(['type', 'message'])
merge(['class' => 'alert alert-'.$type]) }}>
{{ $message }}
## 引入子视图
Blade模版的 `@include` 指令允许你从另一个视图文件中包含一个Blade视图。子视图将继承父视图中的所有可用变量:
@include('shared.errors')
除了可以在子视图中继承父视图中的所有可用数据,你也可以通过数组形式将数据传递给子视图:
@include('view.name', ['some' => 'data'])
如果你尝试 `@include` 一个不存在的视图,Laravel将抛出一个错误。如果你想要去包含一个可能存在但也可能不存在的视图,那么应该使用 `@includeIf` 指令:
@includeIf('view.name', ['some' => 'data'])
如果你想要在给定布尔表达式结果为 `true` 时 `@include` 一个视图,你可以使用 `@includeWhen` 指令:
@includeWhen($boolean, 'view.name', ['some' => 'data'])
如果你想要在给定布尔表达式结果为 `false` 时 `@include` 一个视图,你可以使用 `@includeUnless` 指令:
@includeUnless($boolean, 'view.name', ['some' => 'data'])
要包含给定视图数组中的第一个视图,你可以使用 `@includeFirst` 指令:
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])
> {注意} 你应该避免在视图中使用 `__DIR__` 和 `__FILE__` 常量,因为他们会引用缓存和编译过的视图位置。
#### 给被包含的视图起别名
如果你的 Blade 被包含视图们存储在子目录中,你可能会希望为它们起个易于访问的别名。例如,一个带有如下内容的 Blade 视图内容被存储在 `resources/views/includes/input.blade.php` 文件中:
你可以使用 `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 将作为视图片段中的 `key` 变量。
你也可以向 `@each` 指令传递第四个参数。这个参数是当给定数组为空时要渲染的视图片段。
@each('view.name', $jobs, 'job', 'view.empty')
> {note} 借助 `@each` 渲染视图,无法从父视图中继承变量。如果子视图需要这些变量,就必须使用 `@foreach` 和 `@include` 代替它。
## 堆栈
Blade 允许你将视图压入堆栈,这些视图能够在其它视图或布局中被渲染。这在子视图中指定需要的 JavaScript 库时非常有用:
@push('scripts')
@endpush
如果需要,可以多次压入堆栈。通过向 `@stack` 指令传递堆栈名称来完成堆栈内容的渲染:
@stack('scripts')
如果想要将内容预置在栈顶,需要使用 `@prepend` 指令:
@push('scripts')
This will be second...
@endpush
// Later...
@prepend('scripts')
This will be first...
@endprepend
## Service 注入
The `@inject` 指令可以用于自 Laravel 的 [服务容器](/docs/{{version}}/container)中获取服务。传递给 `@inject` 的第一个参数是将要置入的服务变量名,第二个参数是希望被解析的类或接口名:
@inject('metrics', 'App\Services\MetricsService')
Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
## 扩展 Blade
Blade 允许你使用 `directive` 方法自定义指令。当 Blade 编译器遇到自定义指令时,这会调用该指令包含的表达式提供的回调。
下面的例子创建了 `@datetime($var)` 指令,一个格式化给定的 `DateTime` 的实例 `$var`:
format('m/d/Y H:i'); ?>";
});
}
}
如你所见,我们将在传递给该指令的任意表达式中链式调用 `format` 方法。在这个例子中,该指令将生成如下原生 PHP 代码:
format('m/d/Y H:i'); ?>
> {note} 在更新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