Blade 模板
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>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
指令向布局的 sidebar 追加(而非覆盖)内容。 在渲染视图时,@parent
指令将被布局中的内容替换。
{tip} 和上一个示例相反,这里的
sidebar
片段使用@endsection
代替@show
来结尾。@endsection
指令仅定义了一个片段,@show
则在定义的同时 立即 yield 这个片段。
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
有时候为一个组件定义多个插槽是很有用的。修改 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
。. 通常情况下,这一过程将在 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 }}.
{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:
<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 渲染引擎某个表达式应保持不变。示例如下:
<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 被定义且不是 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
{tip} 循环中可以使用 循环变量 获取循环的可评估信息,比如现在是处于循环的第一次迭代还是最后一次迭代中:
在循环中,还可以终结循环或路过本次迭代:
@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 中:
{{-- 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 表单不能发出 PUT
、 PATCH
及 DELETE
请求,需要加入隐藏的 _method
域来模仿这些 HTTP 动词。Blade 的 @method
指令能够帮你创建这个域:
<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'])
想要包含一个依赖于给定布尔条件的视图,可以使用 @includeWhen
指令:
@includeWhen($boolean, 'view.name', ['some' => 'data'])
要包含给定视图数组中第一个存在的视图,可以使用 includeFirst
指令:
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])
{note} 应当尽量避免在 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 将作为视图片段中的 key
变量。
也可以向 @each
指令传递第四个参数。这个参数是当给定数组为空时要渲染的视图片段。
@each('view.name', $jobs, 'job', 'view.empty')
{note} 借助
@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'); ?>
{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