[TOC]
# Blade 模板
## 简介
Blade 视图文件使用`.blade.php`作为文件扩展名,被存放在`resources/views`目录。可以直接使用原生PHP代码。所有 Blade 视图文件都将被编译成原生的 PHP 代码并缓存,修改后才会重新编译。
## 模板继承
### 定义布局
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>
// 主视图没有内容
@yield('content')
// 主视图有内容
@section('sidebar')
This is the master sidebar.
@show
```
### 扩展布局
```
<!-- 保存在 resources/views/child.blade.php 中 -->
// 继承主视图,目录使用点符号间隔
@extends('layouts.app')
// 纯文本显示
@section('title', 'Page Title')
// 子视图有内容,@parent
显示主视图内容,没有 @parent
则覆盖主视图
@section('sidebar')
@parent
<p>This is appended to the master sidebar.</p>
@endsection
@section('content')
<p>This is my body content.</p>
@endsection
```
>[info] 提示:和上一个示例相反,这里的`sidebar`片段使用`@endsection`代替`@show`来结尾。`@endsection`指令仅定义了一个片段,`@show`则在定义的同时**立即 yield**这个片段。
## 组件 & 插槽
这是一个可复用的「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
```
修改 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('alert', ['foo' => 'bar'])
...
@endcomponent
```
#### 给组件起别名
通常情况,别名在`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']);
});
// Blade视图渲染
Hello, {{ $name }}.
// 可以在 Blade 的回显语句中放置你想要的任意 PHP 代码:
The current UNIX timestamp is {{ time() }}.
```
>[success] 提示:Blade`{{ }}`语句是自动经过 PHP 的`htmlspecialchars`函数传递来防范 XSS 攻击的。
#### 显示非转义字符
默认情况下, Blade 中`{{ }}`语句自动经由 PHP 的`htmlspecialchars`函数传递以防范 XSS 攻击。如果不希望数据被转义,可以使用下面的语法:
```
Hello, {!! $name !!}.
```
>[success] 注意:在回显应用的用户提供的内容时需要谨慎小心。在显示用户提供的数据时,有必要一直使用双花括号语法转义来防范 XSS 攻击。
#### 渲染 JSON
```
// 手动调用
<script>
var app = <?php echo json_encode($array); ?>;
</script>
// 使用`@json`Blade 指令代替手动调用`json_encode`函数
<script>
var app = @json($array);
</script>
```
#### 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 渲染引擎某个表达式应保持不变。`@`符号将被 Blade 删除;在 Blade 引擎中`{{ name }}`表达式将保持不变,取而代之的是 JavaScript 引擎将渲染该表达式。
```
<h1>Laravel</h1>
Hello, @{{ name }}.
```
**`@verbatim`指令**
如果要在大段的模板中显示 JavaScript 变量,可以将 HTML 包裹在`@verbatim`指令中,这样就不需要为每个 Blade 回显语句添加`@`符号:
```
@verbatim
<div class="container">
Hello, {{ name }}.
</div>
@endverbatim
```
## 控制结构
### If 语句
```
@if (count($records) === 1)
I have one record!
@elseif (count($records) > 1)
I have multiple records!
@else
I don't have any records!
@endif
```
unless 除非(!)
```
@unless (Auth::check())
You are not signed in.
@endunless
```
isset 、empty
```
@isset($records)
// $records 被定义且不是 null...
@endisset
@empty($records)
// $records 为空...
@endempty
```
#### 身份验证指令
`@auth`和`@guest`指令能够用于快速确定当前用户是经过身份验证的,还是一个访客:
```
@auth
// 此用户身份已验证...
@endauth
@guest
// 此用户身份未验证...
@endguest
```
校验指定角色
```
@auth('admin')
// 此用户身份已验证...
@endauth
@guest('admin')
// 此用户身份未验证...
@endguest
```
#### 片段指令
可以使用`@hasSection`指令检查片断是否存在内容:
```
// 主视图
@hasSection('navigation')
<div class="pull-right">
@yield('navigation')
</div>
<div class="clearfix"></div>
@endif
// 子视图
@section('navigation')
<p>navigation</p>
@endsection
// 渲染结果
<div class="pull-right">
<p>navigation</p>
</div>
<div class="clearfix"></div>
```
### Switch 指令
```
@switch($i)
@case(1)
First case...
@break
@case(2)
Second case...
@break
@default
Default case...
@endswitch
```
### 循环
```
@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`变量还包含其它几种有用的属性:**
### 注释
Blade 注释不会包含在返回给应用的 HTML 中:
```
{{-- This comment will not be present in the rendered HTML --}}
```
### PHP
可以在模板中使用`@php`指令执行原生的 PHP 代码块:
```
@php
//
@endphp
```
## 表单
### CSRF / Method
HTML 表单不能发出`PUT`、`PATCH`及`DELETE`请求,需要加入隐藏的`_method`域来模仿这些 HTTP 动词。Blade 的`@method`指令能够帮你创建这个域:
```
// 使用`@csrf`Blade 指令来生成令牌字段
<form method="POST" action="/profile">
@method('PUT')
@csrf
</form>
<form method="POST" action="/profile">
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
</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'])
```
想要包含一个依赖于给定布尔条件的视图,可以使用`@includeWhen`指令:
```
@includeWhen($boolean, 'view.name', ['some' => 'data'])
```
要包含给定视图数组中第一个存在的视图,可以使用`includeFirst`指令:
```
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])
```
>[danger] 注意:应当尽量避免在 Blade 视图中使用`__DIR__`和`__FILE__`魔术常量,因为它们将指向缓存中经过编译的视图的位置。
### 给被包含的视图起别名
一个带有如下内容的 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');
```
使用别名渲染
```
@input(['type' => 'email'])
```
### 为集合渲染视图
```
@each('view.name', $jobs, 'job')
```
第一个参数是渲染数组或集合的每个元素的视图片段。第二个参数是希望被迭代的数组或集合,第三个参数则是将被分配给视图中当前迭代的变量名。例如,想要迭代`jobs`数组,通常会在视图片段中使用`job`变量访问每个任务。当前迭代的 key 将作为视图片段中的`key`变量。
也可以向`@each`指令传递第四个参数。这个参数是当给定数组为空时要渲染的视图片段。
```
@each('view.name', $jobs, 'job', 'view.empty')
```
>[danger] 注意:借助`@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 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'); ?>
// 模板使用,显示:22/22/2020 22:22
@datetime(new \DateTime())
```
>[danger] 注意:在更新 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
```
- 入门指南
- 安装
- 部署
- 基础功能
- 路由
- 中间件
- CSRF 保护
- 控制器
- 请求
- 响应
- 视图
- URL
- Session
- 表单验证
- 错误
- 日志
- 前端开发
- Blade 模板
- 本地化
- 脚手架
- 编译资源 Mix
- 安全相关
- 用户认证
- API 认证
- 综合话题
- 命令行
- 广播
- 缓存
- 集合
- 事件
- 文件存储
- 辅助函数
- 邮件发送
- 消息通知
- 扩展包开发
- 队列
- 任务调度
- 数据库
- 快速入门
- 查询构造器
- 分页
- 数据库迁移
- 数据填充
- Redis
- Eloquent ORM
- 快速入门
- 速查表
- Artisan
- Auth
- Blade
- Cache
- Collection
- Composer
- Config
- Container
- Cookie
- DB
- Environment
- Event
- File
- Helper
- Input
- Lang
- Log
- Model
- Pagination
- Queue
- Redirect
- Request
- Response
- Route
- SSH
- Schema
- Security
- Session
- Storage
- String
- URL
- UnitTest
- Validation
- View