跳转至内容

Blade 模板

简介

Blade 是 Laravel 内置的一个简单且强大的模板引擎。与某些 PHP 模板引擎不同,Blade 不会限制你在模板中使用原生 PHP 代码。事实上,所有 Blade 模板都会被编译成原生 PHP 代码并缓存起来,直到被修改,这意味着 Blade 对你的应用程序几乎没有任何开销。Blade 模板文件使用 .blade.php 文件扩展名,通常存储在 resources/views 目录中。

Blade 视图可以通过路由或控制器使用全局 view 辅助函数来返回。当然,如 视图 文档中所述,可以使用 view 辅助函数的第二个参数将数据传递给 Blade 视图。

1Route::get('/', function () {
2 return view('greeting', ['name' => 'Finn']);
3});

通过 Livewire 增强 Blade

想让你的 Blade 模板更上一层楼,并轻松构建动态界面吗?请查看 Laravel Livewire。Livewire 允许你编写带有动态功能的 Blade 组件,这些功能通常只能通过 React、Svelte 或 Vue 等前端框架实现,它提供了一种无需处理复杂性、客户端渲染或构建步骤即可构建现代响应式前端的绝佳方法。

显示数据

你可以通过将变量包裹在大括号中来显示传递给 Blade 视图的数据。例如,假设有以下路由:

1Route::get('/', function () {
2 return view('welcome', ['name' => 'Samantha']);
3});

你可以像这样显示 name 变量的内容:

1Hello, {{ $name }}.

Blade 的 {{ }} echo 语句会自动通过 PHP 的 htmlspecialchars 函数进行处理,以防止 XSS 攻击。

你不仅限于显示传递给视图的变量内容,还可以 echo 任何 PHP 函数的结果。实际上,你可以在 Blade echo 语句中放置任何你想要的 PHP 代码:

1The current UNIX timestamp is {{ time() }}.

HTML 实体编码

默认情况下,Blade(以及 Laravel 的 e 函数)会对 HTML 实体进行双重编码。如果你想禁用双重编码,可以在 AppServiceProviderboot 方法中调用 Blade::withoutDoubleEncoding 方法。

1<?php
2 
3namespace App\Providers;
4 
5use Illuminate\Support\Facades\Blade;
6use Illuminate\Support\ServiceProvider;
7 
8class AppServiceProvider extends ServiceProvider
9{
10 /**
11 * Bootstrap any application services.
12 */
13 public function boot(): void
14 {
15 Blade::withoutDoubleEncoding();
16 }
17}

显示未转义的数据

默认情况下,Blade 的 {{ }} 语句会自动通过 PHP 的 htmlspecialchars 函数进行处理,以防止 XSS 攻击。如果你不希望数据被转义,可以使用以下语法:

1Hello, {!! $name !!}.

在 echo 由应用程序用户提供的内容时,请务必小心。通常应使用转义的双大括号语法,以防止显示用户提供的数据时发生 XSS 攻击。

Blade 与 JavaScript 框架

由于许多 JavaScript 框架也使用“大括号”来指示在浏览器中显示给定的表达式,你可以使用 @ 符号来告知 Blade 渲染引擎某个表达式应保持原样。例如:

1<h1>Laravel</h1>
2 
3Hello, @{{ name }}.

在此示例中,@ 符号会被 Blade 移除;然而,{{ name }} 表达式将不会被 Blade 引擎触碰,从而允许你的 JavaScript 框架对其进行渲染。

@ 符号也可用于转义 Blade 指令:

1{{-- Blade template --}}
2@@if()
3 
4<!-- HTML output -->
5@if()

渲染 JSON

有时你可能希望传递一个数组到视图中,并将其渲染为 JSON 以初始化 JavaScript 变量。例如:

1<script>
2 var app = <?php echo json_encode($array); ?>;
3</script>

然而,无需手动调用 json_encode,你可以使用 Illuminate\Support\Js::from 方法。from 方法接受与 PHP 的 json_encode 函数相同的参数;但它会确保生成的 JSON 已针对 HTML 引号内的包含进行了正确转义。from 方法将返回一个字符串 JSON.parse JavaScript 语句,该语句会将给定的对象或数组转换为有效的 JavaScript 对象。

1<script>
2 var app = {{ Illuminate\Support\Js::from($array) }};
3</script>

最新版本的 Laravel 应用程序骨架中包含一个 Js 门面,它可以在 Blade 模板中方便地使用此功能:

1<script>
2 var app = {{ Js::from($array) }};
3</script>

你应该仅使用 Js::from 方法将现有变量渲染为 JSON。Blade 模板基于正则表达式,尝试向该指令传递复杂的表达式可能会导致意外的失败。

@verbatim 指令

如果你要在模板的大部分区域显示 JavaScript 变量,可以将 HTML 包裹在 @verbatim 指令中,这样就不必为每个 Blade echo 语句添加 @ 符号前缀:

1@verbatim
2 <div class="container">
3 Hello, {{ name }}.
4 </div>
5@endverbatim

Blade 指令

除了模板继承和显示数据外,Blade 还为常见的 PHP 控制结构(如条件语句和循环)提供了方便的快捷方式。这些快捷方式提供了一种非常简洁且易于编写的方式来处理 PHP 控制结构,同时又保持了与原生 PHP 写法的一致性。

If 语句

你可以使用 @if@elseif@else@endif 指令来构建 if 语句。这些指令的功能与其对应的 PHP 语句完全相同:

1@if (count($records) === 1)
2 I have one record!
3@elseif (count($records) > 1)
4 I have multiple records!
5@else
6 I don't have any records!
7@endif

为方便起见,Blade 还提供了 @unless 指令:

1@unless (Auth::check())
2 You are not signed in.
3@endunless

除了已经讨论过的条件指令外,@isset@empty 指令还可以作为它们对应 PHP 函数的便捷快捷方式:

1@isset($records)
2 // $records is defined and is not null...
3@endisset
4 
5@empty($records)
6 // $records is "empty"...
7@endempty

认证指令

@auth@guest 指令可用于快速判断当前用户是否已 认证 或是否为访客:

1@auth
2 // The user is authenticated...
3@endauth
4 
5@guest
6 // The user is not authenticated...
7@endguest

如果需要,在使用 @auth@guest 指令时,可以指定要检查的认证守卫 (guard):

1@auth('admin')
2 // The user is authenticated...
3@endauth
4 
5@guest('admin')
6 // The user is not authenticated...
7@endguest

环境指令

你可以使用 @production 指令检查应用程序是否在生产环境中运行:

1@production
2 // Production specific content...
3@endproduction

或者,你可以使用 @env 指令判断应用程序是否在特定环境中运行:

1@env('staging')
2 // The application is running in "staging"...
3@endenv
4 
5@env(['staging', 'production'])
6 // The application is running in "staging" or "production"...
7@endenv

分段指令

你可以使用 @hasSection 指令判断模板继承片段是否有内容:

1@hasSection('navigation')
2 <div class="pull-right">
3 @yield('navigation')
4 </div>
5 
6 <div class="clearfix"></div>
7@endif

你可以使用 sectionMissing 指令判断片段是否没有内容:

1@sectionMissing('navigation')
2 <div class="pull-right">
3 @include('default-navigation')
4 </div>
5@endif

会话指令

@session 指令可用于判断 会话 值是否存在。如果会话值存在,则 @session@endsession 指令内的模板内容将被执行。在 @session 指令的内容中,你可以 echo $value 变量来显示会话值:

1@session('status')
2 <div class="p-4 bg-green-100">
3 {{ $value }}
4 </div>
5@endsession

上下文指令

@context 指令可用于判断 上下文 (context) 值是否存在。如果上下文值存在,则 @context@endcontext 指令内的模板内容将被执行。在 @context 指令的内容中,你可以 echo $value 变量来显示上下文值:

1@context('canonical')
2 <link href="{{ $value }}" rel="canonical">
3@endcontext

Switch 语句

可以使用 @switch@case@break@default@endswitch 指令构建 Switch 语句:

1@switch($i)
2 @case(1)
3 First case...
4 @break
5 
6 @case(2)
7 Second case...
8 @break
9 
10 @default
11 Default case...
12@endswitch

循环

除了条件语句外,Blade 还提供了处理 PHP 循环结构的简单指令。同样,这些指令中的每一个功能都与它们对应的 PHP 语句完全相同:

1@for ($i = 0; $i < 10; $i++)
2 The current value is {{ $i }}
3@endfor
4 
5@foreach ($users as $user)
6 <p>This is user {{ $user->id }}</p>
7@endforeach
8 
9@forelse ($users as $user)
10 <li>{{ $user->name }}</li>
11@empty
12 <p>No users</p>
13@endforelse
14 
15@while (true)
16 <p>I'm looping forever.</p>
17@endwhile

在遍历 foreach 循环时,你可以使用 循环变量 来获取关于循环的有价值信息,例如你是否处于循环的第一次或最后一次迭代中。

使用循环时,你还可以使用 @continue@break 指令跳过当前迭代或结束循环:

1@foreach ($users as $user)
2 @if ($user->type == 1)
3 @continue
4 @endif
5 
6 <li>{{ $user->name }}</li>
7 
8 @if ($user->number == 5)
9 @break
10 @endif
11@endforeach

你还可以将继续或中断条件包含在指令声明中:

1@foreach ($users as $user)
2 @continue($user->type == 1)
3 
4 <li>{{ $user->name }}</li>
5 
6 @break($user->number == 5)
7@endforeach

循环变量

在遍历 foreach 循环时,循环内将提供 $loop 变量。该变量提供了对一些有用信息的访问,例如当前循环索引以及这是否是循环的第一次或最后一次迭代:

1@foreach ($users as $user)
2 @if ($loop->first)
3 This is the first iteration.
4 @endif
5 
6 @if ($loop->last)
7 This is the last iteration.
8 @endif
9 
10 <p>This is user {{ $user->id }}</p>
11@endforeach

如果你处于嵌套循环中,可以通过 parent 属性访问父循环的 $loop 变量:

1@foreach ($users as $user)
2 @foreach ($user->posts as $post)
3 @if ($loop->parent->first)
4 This is the first iteration of the parent loop.
5 @endif
6 @endforeach
7@endforeach

$loop 变量还包含许多其他有用的属性:

属性 描述
$loop->index 当前循环迭代的索引(从 0 开始)。
$loop->iteration 当前循环迭代次数(从 1 开始)。
$loop->remaining 循环中剩余的迭代次数。
$loop->count 被遍历数组的总项数。
$loop->first 是否是循环的第一次迭代。
$loop->last 是否是循环的最后一次迭代。
$loop->even 是否是循环的偶数次迭代。
$loop->odd 是否是循环的奇数次迭代。
$loop->depth 当前循环的嵌套级别。
$loop->parent 处于嵌套循环时,获取父级循环变量。

条件类名与样式

@class 指令用于有条件地编译 CSS 类名字符串。该指令接受一个类名数组,数组键包含你想要添加的类名,而值是一个布尔表达式。如果数组元素具有数字键,它将始终包含在渲染的类列表中:

1@php
2 $isActive = false;
3 $hasError = true;
4@endphp
5 
6<span @class([
7 'p-4',
8 'font-bold' => $isActive,
9 'text-gray-500' => ! $isActive,
10 'bg-red' => $hasError,
11])></span>
12 
13<span class="p-4 text-gray-500 bg-red"></span>

同样,@style 指令可用于有条件地向 HTML 元素添加内联 CSS 样式:

1@php
2 $isActive = true;
3@endphp
4 
5<span @style([
6 'background-color: red',
7 'font-weight: bold' => $isActive,
8])></span>
9 
10<span style="background-color: red; font-weight: bold;"></span>

附加属性

为方便起见,你可以使用 @checked 指令轻松指示给定的 HTML 复选框输入是否为“选中”。如果提供的条件计算结果为 true,该指令将 echo checked

1<input
2 type="checkbox"
3 name="active"
4 value="active"
5 @checked(old('active', $user->active))
6/>

同样,@selected 指令可用于指示给定的下拉选项是否应为“选中”状态:

1<select name="version">
2 @foreach ($product->versions as $version)
3 <option value="{{ $version }}" @selected(old('version') == $version)>
4 {{ $version }}
5 </option>
6 @endforeach
7</select>

此外,@disabled 指令可用于指示给定的元素是否应为“禁用”状态:

1<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>

而且,@readonly 指令可用于指示给定的元素是否应为“只读”状态:

1<input
2 type="email"
3 name="email"
5 @readonly($user->isNotAdmin())
6/>

另外,@required 指令可用于指示给定的元素是否应为“必填”状态:

1<input
2 type="text"
3 name="title"
4 value="title"
5 @required($user->isAdmin())
6/>

包含子视图

虽然你可以自由使用 @include 指令,但 Blade 组件 提供了类似的功能,并比 @include 指令具有更多优势,例如数据和属性绑定。

Blade 的 @include 指令允许你在一个视图中包含另一个 Blade 视图。父视图中可用的所有变量都将可用于被包含的视图:

1<div>
2 @include('shared.errors')
3 
4 <form>
5 <!-- Form Contents -->
6 </form>
7</div>

尽管被包含的视图将继承父视图中的所有可用数据,但你也可以传递一个额外的数组,该数组中的数据将可用于被包含的视图:

1@include('view.name', ['status' => 'complete'])

如果你尝试 @include 一个不存在的视图,Laravel 将会抛出错误。如果你想包含一个可能存在也可能不存在的视图,你应该使用 @includeIf 指令:

1@includeIf('view.name', ['status' => 'complete'])

如果你想在给定的布尔表达式计算结果为 truefalse@include 一个视图,可以使用 @includeWhen@includeUnless 指令:

1@includeWhen($boolean, 'view.name', ['status' => 'complete'])
2 
3@includeUnless($boolean, 'view.name', ['status' => 'complete'])

要从给定的视图数组中包含第一个存在的视图,可以使用 includeFirst 指令:

1@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])

如果你想包含一个视图而不从父视图继承任何变量,可以使用 @includeIsolated 指令。被包含的视图将仅能访问你明确传递的变量:

1@includeIsolated('view.name', ['user' => $user])

你不应该在 Blade 视图中使用 __DIR____FILE__ 常量,因为它们将指向缓存的编译视图的位置。

为集合渲染视图

你可以使用 Blade 的 @each 指令将循环和包含合并为一行:

1@each('view.name', $jobs, 'job')

@each 指令的第一个参数是为数组或集合中的每个元素渲染的视图。第二个参数是你想要遍历的数组或集合,而第三个参数是在视图中分配给当前迭代的变量名。因此,例如,如果你在遍历一个 jobs 数组,通常你会在视图中将每个 job 作为 job 变量进行访问。当前迭代的数组键在视图中将作为 key 变量可用。

你还可以向 @each 指令传递第四个参数。该参数决定了如果给定的数组为空时要渲染的视图。

1@each('view.name', $jobs, 'job', 'view.empty')

通过 @each 渲染的视图不会从父视图继承变量。如果子视图需要这些变量,你应该改用 @foreach@include 指令。

@once 指令

@once 指令允许你定义一个在每个渲染周期中只会被执行一次的模板片段。这对于使用 堆栈 (stacks) 将一段特定的 JavaScript 推送到页面头部非常有用。例如,如果你在一个循环中渲染一个特定的 组件,你可能只希望在组件第一次被渲染时将 JavaScript 推送到头部:

1@once
2 @push('scripts')
3 <script>
4 // Your custom JavaScript...
5 </script>
6 @endpush
7@endonce

由于 @once 指令经常与 @push@prepend 指令结合使用,为了方便起见,提供了 @pushOnce@prependOnce 指令:

1@pushOnce('scripts')
2 <script>
3 // Your custom JavaScript...
4 </script>
5@endPushOnce

如果你从两个不同的 Blade 模板推送重复的内容,你应该向 @pushOnce 指令的第二个参数提供一个唯一标识符,以确保内容只会被渲染一次:

1<!-- pie-chart.blade.php -->
2@pushOnce('scripts', 'chart.js')
3 <script src="/chart.js"></script>
4@endPushOnce
5 
6<!-- line-chart.blade.php -->
7@pushOnce('scripts', 'chart.js')
8 <script src="/chart.js"></script>
9@endPushOnce

原生 PHP

在某些情况下,在视图中嵌入 PHP 代码非常有用。你可以使用 Blade 的 @php 指令在模板中执行一块原生 PHP 代码:

1@php
2 $counter = 1;
3@endphp

或者,如果你只需要使用 PHP 来导入类,可以使用 @use 指令:

1@use('App\Models\Flight')

可以向 @use 指令提供第二个参数来为导入的类起别名:

1@use('App\Models\Flight', 'FlightModel')

如果你在同一个命名空间下有多个类,可以将这些类的导入进行分组:

1@use('App\Models\{Flight, Airport}')

@use 指令还支持通过在导入路径前加上 functionconst 修饰符来导入 PHP 函数和常量:

1@use(function App\Helpers\format_currency)
2@use(const App\Constants\MAX_ATTEMPTS)

就像类导入一样,函数和常量也支持别名:

1@use(function App\Helpers\format_currency, 'formatMoney')
2@use(const App\Constants\MAX_ATTEMPTS, 'MAX_TRIES')

函数和常量修饰符也支持分组导入,允许你在单个指令中从同一个命名空间导入多个符号:

1@use(function App\Helpers\{format_currency, format_date})
2@use(const App\Constants\{MAX_ATTEMPTS, DEFAULT_TIMEOUT})

注释

Blade 还允许你在视图中定义注释。然而,与 HTML 注释不同,Blade 注释不会包含在应用程序返回的 HTML 中:

1{{-- This comment will not be present in the rendered HTML --}}

组件

组件和插槽提供了与分段 (sections)、布局 (layouts) 和包含 (includes) 类似的优势;然而,有些人可能觉得组件和插槽的心智模型更容易理解。编写组件有两种方法:基于类的组件和匿名组件。

要创建一个基于类的组件,你可以使用 make:component Artisan 命令。为了演示如何使用组件,我们将创建一个简单的 Alert 组件。make:component 命令将把该组件放置在 app/View/Components 目录中:

1php artisan make:component Alert

make:component 命令还将为组件创建一个视图模板。视图将被放置在 resources/views/components 目录中。在为你自己的应用程序编写组件时,组件会自动在 app/View/Components 目录和 resources/views/components 目录中被发现,因此通常不需要进一步注册组件。

你也可以在子目录中创建组件:

1php artisan make:component Forms/Input

上面的命令将在 app/View/Components/Forms 目录中创建一个 Input 组件,视图将被放置在 resources/views/components/forms 目录中。

手动注册包组件

在为你自己的应用程序编写组件时,组件会自动在 app/View/Components 目录和 resources/views/components 目录中被发现。

但是,如果你正在构建一个利用 Blade 组件的包,则需要手动注册你的组件类及其 HTML 标签别名。你通常应该在包的服务提供者的 boot 方法中注册你的组件:

1use Illuminate\Support\Facades\Blade;
2 
3/**
4 * Bootstrap your package's services.
5 */
6public function boot(): void
7{
8 Blade::component('package-alert', Alert::class);
9}

一旦你的组件被注册,它就可以使用其标签别名进行渲染:

1<x-package-alert/>

或者,你可以使用 componentNamespace 方法按惯例自动加载组件类。例如,一个 Nightshade 包可能拥有位于 Package\Views\Components 命名空间内的 CalendarColorPicker 组件:

1use Illuminate\Support\Facades\Blade;
2 
3/**
4 * Bootstrap your package's services.
5 */
6public function boot(): void
7{
8 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
9}

这将允许使用 package-name:: 语法通过其供应商命名空间来使用包组件:

1<x-nightshade::calendar />
2<x-nightshade::color-picker />

Blade 将通过对组件名称进行帕斯卡拼写法 (Pascal-casing) 来自动检测链接到此组件的类。子目录也支持使用“点”符号。

渲染组件

要显示一个组件,你可以在 Blade 模板中使用 Blade 组件标签。Blade 组件标签以字符串 x- 开头,后跟组件类的 kebab-case 名称:

1<x-alert/>
2 
3<x-user-profile/>

如果组件类嵌套在 app/View/Components 目录中更深的位置,你可以使用 . 字符来表示目录嵌套。例如,如果我们假设一个组件位于 app/View/Components/Inputs/Button.php,我们可以这样渲染它:

1<x-inputs.button/>

如果你想有条件地渲染组件,可以在组件类中定义一个 shouldRender 方法。如果 shouldRender 方法返回 false,则组件将不会被渲染:

1use Illuminate\Support\Str;
2 
3/**
4 * Whether the component should be rendered
5 */
6public function shouldRender(): bool
7{
8 return Str::length($this->message) > 0;
9}

索引组件

有时组件属于一个组件组,你可能希望将相关组件组合在一个目录中。例如,想象一个具有以下类结构的“card”组件:

1App\Views\Components\Card\Card
2App\Views\Components\Card\Header
3App\Views\Components\Card\Body

由于根 Card 组件嵌套在 Card 目录中,你可能认为需要通过 <x-card.card> 来渲染该组件。然而,当组件的文件名与组件目录的名称匹配时,Laravel 会自动认为该组件是“根”组件,并允许你在不重复目录名称的情况下渲染组件:

1<x-card>
2 <x-card.header>...</x-card.header>
3 <x-card.body>...</x-card.body>
4</x-card>

向组件传递数据

你可以使用 HTML 属性将数据传递给 Blade 组件。硬编码的原始值可以使用简单的 HTML 属性字符串传递给组件。PHP 表达式和变量应该通过使用 : 字符作为前缀的属性传递给组件:

1<x-alert type="error" :message="$message"/>

你应该在组件的类构造函数中定义所有数据属性。组件上的所有公共属性将自动可用于组件的视图。不需要从组件的 render 方法将数据传递给视图。

1<?php
2 
3namespace App\View\Components;
4 
5use Illuminate\View\Component;
6use Illuminate\View\View;
7 
8class Alert extends Component
9{
10 /**
11 * Create the component instance.
12 */
13 public function __construct(
14 public string $type,
15 public string $message,
16 ) {}
17 
18 /**
19 * Get the view / contents that represent the component.
20 */
21 public function render(): View
22 {
23 return view('components.alert');
24 }
25}

当渲染组件时,你可以通过按名称 echo 变量来显示组件公共变量的内容:

1<div class="alert alert-{{ $type }}">
2 {{ $message }}
3</div>

大小写

组件构造函数参数应使用 camelCase 指定,而在 HTML 属性中引用参数名称时应使用 kebab-case。例如,给定以下组件构造函数:

1/**
2 * Create the component instance.
3 */
4public function __construct(
5 public string $alertType,
6) {}

$alertType 参数可以这样提供给组件:

1<x-alert alert-type="danger" />

短属性语法

在将属性传递给组件时,你还可以使用“短属性”语法。这通常很方便,因为属性名称经常与它们对应的变量名称匹配:

1{{-- Short attribute syntax... --}}
2<x-profile :$userId :$name />
3 
4{{-- Is equivalent to... --}}
5<x-profile :user-id="$userId" :name="$name" />

转义属性渲染

由于一些 JavaScript 框架(如 Alpine.js)也使用冒号前缀的属性,你可以使用双冒号 (::) 前缀来告知 Blade 该属性不是 PHP 表达式。例如,给定以下组件:

1<x-button ::class="{ danger: isDeleting }">
2 Submit
3</x-button>

以下 HTML 将由 Blade 渲染:

1<button :class="{ danger: isDeleting }">
2 Submit
3</button>

组件方法

除了公共变量可用于你的组件模板外,还可以调用组件上的任何公共方法。例如,想象一个具有 isSelected 方法的组件:

1/**
2 * Determine if the given option is the currently selected option.
3 */
4public function isSelected(string $option): bool
5{
6 return $option === $this->selected;
7}

你可以通过调用与方法名称匹配的变量来从组件模板执行此方法:

1<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}">
2 {{ $label }}
3</option>

在组件类中访问属性和插槽

Blade 组件还允许你在类的 render 方法内访问组件名称、属性和插槽。但是,为了访问这些数据,你应该从组件的 render 方法中返回一个闭包:

1use Closure;
2 
3/**
4 * Get the view / contents that represent the component.
5 */
6public function render(): Closure
7{
8 return function () {
9 return '<div {{ $attributes }}>Components content</div>';
10 };
11}

组件的 render 方法返回的闭包也可以接收一个 $data 数组作为其唯一参数。该数组将包含几个提供关于组件信息的元素:

1return function (array $data) {
2 // $data['componentName'];
3 // $data['attributes'];
4 // $data['slot'];
5 
6 return '<div {{ $attributes }}>Components content</div>';
7}

$data 数组中的元素不应直接嵌入到 render 方法返回的 Blade 字符串中,因为这样做可能会允许通过恶意属性内容进行远程代码执行。

componentName 等于 HTML 标签中 x- 前缀之后的名称。因此 <x-alert />componentName 将为 alertattributes 元素将包含 HTML 标签上存在的所有属性。slot 元素是一个包含组件插槽内容的 Illuminate\Support\HtmlString 实例。

闭包应该返回一个字符串。如果返回的字符串对应于现有视图,则该视图将被渲染;否则,返回的字符串将被评估为内联 Blade 视图。

附加依赖项

如果你的组件需要来自 Laravel 服务容器 的依赖项,你可以在任何组件数据属性之前列出它们,它们将由容器自动注入:

1use App\Services\AlertCreator;
2 
3/**
4 * Create the component instance.
5 */
6public function __construct(
7 public AlertCreator $creator,
8 public string $type,
9 public string $message,
10) {}

隐藏属性 / 方法

如果你想阻止某些公共方法或属性作为变量暴露给组件模板,可以将它们添加到组件的 $except 数组属性中:

1<?php
2 
3namespace App\View\Components;
4 
5use Illuminate\View\Component;
6 
7class Alert extends Component
8{
9 /**
10 * The properties / methods that should not be exposed to the component template.
11 *
12 * @var array
13 */
14 protected $except = ['type'];
15 
16 /**
17 * Create the component instance.
18 */
19 public function __construct(
20 public string $type,
21 ) {}
22}

组件属性

我们已经研究了如何将数据属性传递给组件;然而,有时你可能需要指定额外的 HTML 属性(例如 class),这些属性不是组件功能所需的数据的一部分。通常,你希望将这些额外的属性向下传递到组件模板的根元素。例如,想象我们想这样渲染一个 alert 组件:

1<x-alert type="error" :message="$message" class="mt-4"/>

所有不属于组件构造函数的属性都将自动添加到组件的“属性包 (attribute bag)”中。此属性包通过 $attributes 变量自动提供给组件。所有属性都可以通过 echo 此变量在组件内进行渲染:

1<div {{ $attributes }}>
2 <!-- Component content -->
3</div>

目前不支持在组件标签内使用 @env 等指令。例如,<x-alert :live="@env('production')"/> 将不会被编译。

默认 / 合并属性

有时你可能需要为属性指定默认值,或者将额外的值合并到组件的某些属性中。要实现这一点,你可以使用属性包的 merge 方法。此方法对于定义一组应始终应用于组件的默认 CSS 类特别有用:

1<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
2 {{ $message }}
3</div>

如果我们假设该组件的使用方式如下:

1<x-alert type="error" :message="$message" class="mb-4"/>

组件的最终渲染 HTML 将如下所示:

1<div class="alert alert-error mb-4">
2 <!-- Contents of the $message variable -->
3</div>

有条件地合并类名

有时你可能希望在给定条件为 true 时合并类名。你可以通过 class 方法实现这一点,该方法接受一个类名数组,数组键包含你想要添加的类名,而值是一个布尔表达式。如果数组元素具有数字键,它将始终包含在渲染的类列表中:

1<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}>
2 {{ $message }}
3</div>

如果你需要将其他属性合并到你的组件上,可以将 merge 方法链接到 class 方法上:

1<button {{ $attributes->class(['p-4'])->merge(['type' => 'button']) }}>
2 {{ $slot }}
3</button>

如果你需要对不应接收合并属性的其他 HTML 元素有条件地编译类名,可以使用 @class 指令

非 Class 属性合并

在合并非 class 属性时,提供给 merge 方法的值将被视为该属性的“默认”值。然而,与 class 属性不同,这些属性不会与注入的属性值合并。相反,它们会被覆盖。例如,一个 button 组件的实现可能如下所示:

1<button {{ $attributes->merge(['type' => 'button']) }}>
2 {{ $slot }}
3</button>

要渲染带有自定义 type 的按钮组件,可以在使用该组件时指定它。如果未指定类型,将使用 button 类型:

1<x-button type="submit">
2 Submit
3</x-button>

此示例中 button 组件渲染的 HTML 将为:

1<button type="submit">
2 Submit
3</button>

如果你希望 class 以外的属性将其默认值和注入值连接在一起,可以使用 prepends 方法。在此示例中,data-controller 属性将始终以 profile-controller 开头,任何额外的注入 data-controller 值将放在此默认值之后:

1<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}>
2 {{ $slot }}
3</div>

检索和过滤属性

你可以使用 filter 方法过滤属性。该方法接受一个闭包,如果你希望在属性包中保留该属性,该闭包应返回 true

1{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }}

为方便起见,你可以使用 whereStartsWith 方法检索所有键以给定字符串开头的属性:

1{{ $attributes->whereStartsWith('wire:model') }}

相反,whereDoesntStartWith 方法可用于排除所有键以给定字符串开头的属性:

1{{ $attributes->whereDoesntStartWith('wire:model') }}

使用 first 方法,你可以渲染给定属性包中的第一个属性:

1{{ $attributes->whereStartsWith('wire:model')->first() }}

如果你想检查属性是否存在于组件上,可以使用 has 方法。该方法接受属性名称作为其唯一参数,并返回一个布尔值,指示属性是否存在:

1@if ($attributes->has('class'))
2 <div>Class attribute is present</div>
3@endif

如果将数组传递给 has 方法,该方法将确定所有给定的属性是否都存在于组件上:

1@if ($attributes->has(['name', 'class']))
2 <div>All of the attributes are present</div>
3@endif

hasAny 方法可用于确定给定的属性中是否有任何属性存在于组件上:

1@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
2 <div>One of the attributes is present</div>
3@endif

你可以使用 get 方法检索特定属性的值:

1{{ $attributes->get('class') }}

only 方法可用于仅检索具有给定键的属性:

1{{ $attributes->only(['class']) }}

except 方法可用于检索除具有给定键的属性之外的所有属性:

1{{ $attributes->except(['class']) }}

保留关键字

默认情况下,一些关键字被保留用于 Blade 的内部使用以渲染组件。以下关键字不能在你的组件中定义为公共属性或方法名称:

  • data
  • render
  • resolve
  • resolveView
  • shouldRender
  • view
  • withAttributes
  • withName

插槽 (Slots)

你通常需要通过“插槽 (slots)”将额外的内容传递给组件。组件插槽通过 echo $slot 变量来渲染。为了探索这个概念,让我们想象一个 alert 组件具有以下标记:

1<!-- /resources/views/components/alert.blade.php -->
2 
3<div class="alert alert-danger">
4 {{ $slot }}
5</div>

我们可以通过将内容注入组件来将内容传递给 slot

1<x-alert>
2 <strong>Whoops!</strong> Something went wrong!
3</x-alert>

有时组件可能需要在组件内的不同位置渲染多个不同的插槽。让我们修改我们的 alert 组件以允许注入“title”插槽:

1<!-- /resources/views/components/alert.blade.php -->
2 
3<span class="alert-title">{{ $title }}</span>
4 
5<div class="alert alert-danger">
6 {{ $slot }}
7</div>

你可以使用 x-slot 标签定义命名插槽的内容。任何不在显式 x-slot 标签内的内容都将通过 $slot 变量传递给组件:

1<x-alert>
2 <x-slot:title>
3 Server Error
4 </x-slot>
5 
6 <strong>Whoops!</strong> Something went wrong!
7</x-alert>

你可以调用插槽的 isEmpty 方法来判断插槽是否包含内容:

1<span class="alert-title">{{ $title }}</span>
2 
3<div class="alert alert-danger">
4 @if ($slot->isEmpty())
5 This is default content if the slot is empty.
6 @else
7 {{ $slot }}
8 @endif
9</div>

此外,hasActualContent 方法可用于判断插槽是否包含任何不是 HTML 注释的“实际”内容:

1@if ($slot->hasActualContent())
2 The scope has non-comment content.
3@endif

作用域插槽

如果你使用过 Vue 等 JavaScript 框架,你可能熟悉“作用域插槽”,它允许你在插槽内访问组件中的数据或方法。你可以在 Laravel 中通过在组件上定义公共方法或属性,并通过 $component 变量在插槽内访问该组件来实现类似的行为。在此示例中,我们将假设 x-alert 组件在其组件类上定义了一个公共的 formatAlert 方法:

1<x-alert>
2 <x-slot:title>
3 {{ $component->formatAlert('Server Error') }}
4 </x-slot>
5 
6 <strong>Whoops!</strong> Something went wrong!
7</x-alert>

插槽属性

与 Blade 组件一样,你可以为插槽分配额外的 属性,例如 CSS 类名:

1<x-card class="shadow-sm">
2 <x-slot:heading class="font-bold">
3 Heading
4 </x-slot>
5 
6 Content
7 
8 <x-slot:footer class="text-sm">
9 Footer
10 </x-slot>
11</x-card>

要与插槽属性交互,你可以访问插槽变量的 attributes 属性。有关如何与属性交互的更多信息,请参阅关于 组件属性 的文档。

1@props([
2 'heading',
3 'footer',
4])
5 
6<div {{ $attributes->class(['border']) }}>
7 <h1 {{ $heading->attributes->class(['text-lg']) }}>
8 {{ $heading }}
9 </h1>
10 
11 {{ $slot }}
12 
13 <footer {{ $footer->attributes->class(['text-gray-700']) }}>
14 {{ $footer }}
15 </footer>
16</div>

内联组件视图

对于非常小的组件,管理组件类和组件视图模板可能会感觉很繁琐。因此,你可以直接从 render 方法返回组件的标记:

1/**
2 * Get the view / contents that represent the component.
3 */
4public function render(): string
5{
6 return <<<'blade'
7 <div class="alert alert-danger">
8 {{ $slot }}
9 </div>
10 blade;
11}

生成内联视图组件

要创建一个渲染内联视图的组件,你可以在执行 make:component 命令时使用 inline 选项:

1php artisan make:component Alert --inline

动态组件

有时你可能需要渲染一个组件,但在运行时才知道应该渲染哪个组件。在这种情况下,你可以使用 Laravel 内置的 dynamic-component 组件来根据运行时值或变量渲染组件:

1// $componentName = "secondary-button";
2 
3<x-dynamic-component :component="$componentName" class="mt-4" />

手动注册组件

以下关于手动注册组件的文档主要适用于那些正在编写包含视图组件的 Laravel 包的用户。如果你没有编写包,组件文档的这一部分可能与你无关。

在为你自己的应用程序编写组件时,组件会自动在 app/View/Components 目录和 resources/views/components 目录中被发现。

但是,如果你正在构建一个利用 Blade 组件的包,或者将组件放置在非传统目录中,则需要手动注册你的组件类及其 HTML 标签别名,以便 Laravel 知道在哪里查找组件。你通常应该在包的服务提供者的 boot 方法中注册你的组件:

1use Illuminate\Support\Facades\Blade;
2use VendorPackage\View\Components\AlertComponent;
3 
4/**
5 * Bootstrap your package's services.
6 */
7public function boot(): void
8{
9 Blade::component('package-alert', AlertComponent::class);
10}

一旦你的组件被注册,它就可以使用其标签别名进行渲染:

1<x-package-alert/>

自动加载包组件

或者,你可以使用 componentNamespace 方法按惯例自动加载组件类。例如,一个 Nightshade 包可能拥有位于 Package\Views\Components 命名空间内的 CalendarColorPicker 组件:

1use Illuminate\Support\Facades\Blade;
2 
3/**
4 * Bootstrap your package's services.
5 */
6public function boot(): void
7{
8 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
9}

这将允许使用 package-name:: 语法通过其供应商命名空间来使用包组件:

1<x-nightshade::calendar />
2<x-nightshade::color-picker />

Blade 将通过对组件名称进行帕斯卡拼写法 (Pascal-casing) 来自动检测链接到此组件的类。子目录也支持使用“点”符号。

匿名组件

与内联组件类似,匿名组件提供了一种通过单个文件管理组件的机制。但是,匿名组件使用单个视图文件,并且没有关联的类。要定义匿名组件,你只需要在 resources/views/components 目录中放置一个 Blade 模板。例如,假设你在 resources/views/components/alert.blade.php 中定义了一个组件,你可以直接这样渲染它:

1<x-alert/>

你可以使用 . 字符来指示组件是否嵌套在 components 目录更深处。例如,假设该组件定义在 resources/views/components/inputs/button.blade.php 中,你可以这样渲染它:

1<x-inputs.button/>

要通过 Artisan 创建匿名组件,可以在调用 make:component 命令时使用 --view 标志:

1php artisan make:component forms.input --view

上面的命令将在 resources/views/components/forms/input.blade.php 创建一个 Blade 文件,该文件可以作为组件通过 <x-forms.input /> 进行渲染。

匿名索引组件

有时,当一个组件由许多 Blade 模板组成时,你可能希望将给定组件的模板分组在一个目录中。例如,想象一个具有以下目录结构的“accordion”组件:

1/resources/views/components/accordion.blade.php
2/resources/views/components/accordion/item.blade.php

此目录结构允许你渲染 accordion 组件及其子项,如下所示:

1<x-accordion>
2 <x-accordion.item>
3 ...
4 </x-accordion.item>
5</x-accordion>

然而,为了通过 x-accordion 渲染 accordion 组件,我们被迫将“索引”accordion 组件模板放置在 resources/views/components 目录中,而不是将其与 accordion 相关的其他模板一起嵌套在 accordion 目录中。

幸运的是,Blade 允许你在组件目录本身内放置一个与组件目录名称匹配的文件。当该模板存在时,即使它嵌套在目录中,它也可以被渲染为组件的“根”元素。因此,我们可以继续使用上述示例中给出的相同 Blade 语法;但是,我们将调整目录结构如下:

1/resources/views/components/accordion/accordion.blade.php
2/resources/views/components/accordion/item.blade.php

数据属性 / 特性

由于匿名组件没有任何关联的类,你可能想知道如何区分哪些数据应作为变量传递给组件,哪些属性应放置在组件的 属性包 中。

你可以使用组件 Blade 模板顶部的 @props 指令指定哪些属性应被视为数据变量。组件上的所有其他属性将通过组件的属性包可用。如果你希望给数据变量一个默认值,可以将变量名称指定为数组键,并将默认值指定为数组值:

1<!-- /resources/views/components/alert.blade.php -->
2 
3@props(['type' => 'info', 'message'])
4 
5<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
6 {{ $message }}
7</div>

给定上面的组件定义,我们可以这样渲染组件:

1<x-alert type="error" :message="$message" class="mb-4"/>

访问父级数据

有时你可能想在子组件内访问父组件的数据。在这些情况下,可以使用 @aware 指令。例如,想象我们正在构建一个由父级 <x-menu> 和子级 <x-menu.item> 组成的复杂菜单组件:

1<x-menu color="purple">
2 <x-menu.item>...</x-menu.item>
3 <x-menu.item>...</x-menu.item>
4</x-menu>

<x-menu> 组件可能具有如下实现:

1<!-- /resources/views/components/menu/index.blade.php -->
2 
3@props(['color' => 'gray'])
4 
5<ul {{ $attributes->merge(['class' => 'bg-'.$color.'-200']) }}>
6 {{ $slot }}
7</ul>

因为 color prop 仅传递给了父级 (<x-menu>),所以它在 <x-menu.item> 内将不可用。但是,如果我们使用 @aware 指令,我们也可以使其在 <x-menu.item> 内可用:

1<!-- /resources/views/components/menu/item.blade.php -->
2 
3@aware(['color' => 'gray'])
4 
5<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
6 {{ $slot }}
7</li>

@aware 指令无法访问未通过 HTML 属性明确传递给父组件的父级数据。未明确传递给父组件的默认 @props 值不能被 @aware 指令访问。

匿名组件路径

如前所述,匿名组件通常通过在 resources/views/components 目录中放置 Blade 模板来定义。但是,除了默认路径外,你偶尔可能还想向 Laravel 注册其他匿名组件路径。

anonymousComponentPath 方法接受匿名组件位置的“路径”作为其第一个参数,以及可选的组件应放置在下的“命名空间”作为其第二个参数。通常,此方法应从应用程序的 服务提供者 之一的 boot 方法中调用:

1/**
2 * Bootstrap any application services.
3 */
4public function boot(): void
5{
6 Blade::anonymousComponentPath(__DIR__.'/../components');
7}

当组件路径在没有指定前缀的情况下注册时,如上例所示,它们也可以在你的 Blade 组件中渲染而无需对应的前缀。例如,如果 panel.blade.php 组件存在于上述注册的路径中,它可以这样渲染:

1<x-panel />

前缀“命名空间”可以作为第二个参数提供给 anonymousComponentPath 方法:

1Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');

当提供前缀时,该“命名空间”内的组件可以通过在渲染组件时将组件的命名空间作为组件名称的前缀来渲染:

1<x-dashboard::panel />

构建布局

使用组件构建布局

大多数 Web 应用程序在不同页面上保持相同的总体布局。如果我们必须在我们创建的每个视图中重复整个布局 HTML,那将是非常麻烦且难以维护的。值得庆幸的是,将此布局定义为单个 Blade 组件 然后在我们的应用程序中全面使用它是非常方便的。

定义布局组件

例如,想象我们正在构建一个“待办事项”列表应用程序。我们可能会定义一个如下所示的 layout 组件:

1<!-- resources/views/components/layout.blade.php -->
2 
3<html>
4 <head>
5 <title>{{ $title ?? 'Todo Manager' }}</title>
6 </head>
7 <body>
8 <h1>Todos</h1>
9 <hr/>
10 {{ $slot }}
11 </body>
12</html>

应用布局组件

一旦定义了 layout 组件,我们就可以创建一个利用该组件的 Blade 视图。在此示例中,我们将定义一个简单的视图来显示我们的任务列表:

1<!-- resources/views/tasks.blade.php -->
2 
3<x-layout>
4 @foreach ($tasks as $task)
5 <div>{{ $task }}</div>
6 @endforeach
7</x-layout>

请记住,注入组件的内容将被提供给我们的 layout 组件中的默认 $slot 变量。正如你可能已经注意到的,我们的 layout 还尊重一个 $title 插槽(如果提供了的话);否则,将显示默认标题。我们可以使用 组件文档 中讨论的标准插槽语法从我们的任务列表视图中注入自定义标题。

1<!-- resources/views/tasks.blade.php -->
2 
3<x-layout>
4 <x-slot:title>
5 Custom Title
6 </x-slot>
7 
8 @foreach ($tasks as $task)
9 <div>{{ $task }}</div>
10 @endforeach
11</x-layout>

现在我们已经定义了布局和任务列表视图,我们只需要从路由中返回 task 视图即可:

1use App\Models\Task;
2 
3Route::get('/tasks', function () {
4 return view('tasks', ['tasks' => Task::all()]);
5});

使用模板继承构建布局

定义布局

布局也可以通过“模板继承”来创建。这是在引入 组件 之前构建应用程序的主要方式。

为了入门,让我们看一个简单的示例。首先,我们将检查一个页面布局。由于大多数 Web 应用程序在不同页面上保持相同的总体布局,因此将此布局定义为单个 Blade 视图很方便:

1<!-- resources/views/layouts/app.blade.php -->
2 
3<html>
4 <head>
5 <title>App Name - @yield('title')</title>
6 </head>
7 <body>
8 @section('sidebar')
9 This is the master sidebar.
10 @show
11 
12 <div class="container">
13 @yield('content')
14 </div>
15 </body>
16</html>

如你所见,此文件包含典型的 HTML 标记。然而,请注意 @section@yield 指令。顾名思义,@section 指令定义了一段内容,而 @yield 指令用于显示给定分段的内容。

现在我们已经为我们的应用程序定义了布局,让我们定义一个继承该布局的子页面。

扩展布局

在定义子视图时,使用 @extends Blade 指令来指定子视图应该“继承”哪个布局。扩展 Blade 布局的视图可以使用 @section 指令将内容注入布局的分段中。记住,如上面的示例所示,这些分段的内容将使用 @yield 显示在布局中。

1<!-- resources/views/child.blade.php -->
2 
3@extends('layouts.app')
4 
5@section('title', 'Page Title')
6 
7@section('sidebar')
8 @@parent
9 
10 <p>This is appended to the master sidebar.</p>
11@endsection
12 
13@section('content')
14 <p>This is my body content.</p>
15@endsection

在此示例中,sidebar 分段利用了 @@parent 指令来追加(而不是覆盖)内容到布局的侧边栏。当视图渲染时,@@parent 指令将被布局的内容所替换。

与之前的示例相反,此 sidebar 分段以 @endsection 而不是 @show 结尾。@endsection 指令只会定义一个分段,而 @show 则会定义并 立即渲染 该分段。

@yield 指令也接受一个默认值作为其第二个参数。如果正在渲染的分段未定义,则将渲染此值。

1@yield('content', 'Default content')

表单

CSRF 字段

每当你在应用程序中定义 HTML 表单时,你应该在表单中包含一个隐藏的 CSRF 令牌字段,以便 CSRF 保护 中间件可以验证请求。你可以使用 @csrf Blade 指令生成令牌字段:

1<form method="POST" action="/profile">
2 @csrf
3 
4 ...
5</form>

Method 字段

由于 HTML 表单不能发出 PUTPATCHDELETE 请求,因此你需要添加一个隐藏的 _method 字段来伪造这些 HTTP 动词。@method Blade 指令可以为你创建此字段:

1<form action="/foo/bar" method="POST">
2 @method('PUT')
3 
4 ...
5</form>

验证错误

@error 指令可用于快速检查给定属性是否存在 验证错误消息。在 @error 指令内,你可以 echo $message 变量来显示错误消息:

1<!-- /resources/views/post/create.blade.php -->
2 
3<label for="title">Post Title</label>
4 
5<input
6 id="title"
7 type="text"
8 class="@error('title') is-invalid @enderror"
9/>
10 
11@error('title')
12 <div class="alert alert-danger">{{ $message }}</div>
13@enderror

由于 @error 指令会被编译为“if”语句,你可以使用 @else 指令在属性没有错误时渲染内容:

1<!-- /resources/views/auth.blade.php -->
2 
3<label for="email">Email address</label>
4 
5<input
6 id="email"
7 type="email"
8 class="@error('email') is-invalid @else is-valid @enderror"
9/>

你可以将 特定的错误包名称 作为第二个参数传递给 @error 指令,以便在包含多个表单的页面上检索验证错误消息:

1<!-- /resources/views/auth.blade.php -->
2 
3<label for="email">Email address</label>
4 
5<input
6 id="email"
7 type="email"
8 class="@error('email', 'login') is-invalid @enderror"
9/>
10 
11@error('email', 'login')
12 <div class="alert alert-danger">{{ $message }}</div>
13@enderror

堆栈 (Stacks)

Blade 允许你推送到命名堆栈,这些堆栈可以在另一个视图或布局中的其他地方渲染。这对于指定子视图所需的任何 JavaScript 库特别有用:

1@push('scripts')
2 <script src="/example.js"></script>
3@endpush

如果你想在给定布尔表达式计算结果为 true@push 内容,可以使用 @pushIf 指令:

1@pushIf($shouldPush, 'scripts')
2 <script src="/example.js"></script>
3@endPushIf

你可以根据需要多次推送到堆栈。要渲染完整的堆栈内容,请将堆栈名称传递给 @stack 指令:

1<head>
2 <!-- Head Contents -->
3 
4 @stack('scripts')
5</head>

如果你想将内容预置到堆栈的开头,你应该使用 @prepend 指令:

1@push('scripts')
2 This will be second...
3@endpush
4 
5// Later...
6 
7@prepend('scripts')
8 This will be first...
9@endprepend

@hasstack 指令可用于判断堆栈是否为空:

1@hasstack('list')
2 <ul>
3 @stack('list')
4 </ul>
5@endif

服务注入

@inject 指令可用于从 Laravel 服务容器 检索服务。传递给 @inject 的第一个参数是服务将被放入的变量名称,而第二个参数是你希望解析的服务的类或接口名称:

1@inject('metrics', 'App\Services\MetricsService')
2 
3<div>
4 Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
5</div>

渲染内联 Blade 模板

有时你可能需要将原始 Blade 模板字符串转换为有效的 HTML。你可以使用 Blade 门面提供的 render 方法来实现这一点。render 方法接受 Blade 模板字符串以及可选的要提供给模板的数据数组:

1use Illuminate\Support\Facades\Blade;
2 
3return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);

Laravel 通过将内联 Blade 模板写入 storage/framework/views 目录来渲染它们。如果你希望 Laravel 在渲染 Blade 模板后删除这些临时文件,你可以向方法提供 deleteCachedView 参数:

1return Blade::render(
2 'Hello, {{ $name }}',
3 ['name' => 'Julian Bashir'],
4 deleteCachedView: true
5);

渲染 Blade 片段

当使用 Turbohtmx 等前端框架时,你有时可能只需要在 HTTP 响应中返回 Blade 模板的一部分。Blade “片段 (fragments)”允许你做到这一点。要入门,请将 Blade 模板的一部分放置在 @fragment@endfragment 指令中:

1@fragment('user-list')
2 <ul>
3 @foreach ($users as $user)
4 <li>{{ $user->name }}</li>
5 @endforeach
6 </ul>
7@endfragment

然后,在渲染利用此模板的视图时,你可以调用 fragment 方法来指定仅应在发出的 HTTP 响应中包含指定的片段:

1return view('dashboard', ['users' => $users])->fragment('user-list');

fragmentIf 方法允许你根据给定的条件有条件地返回视图片段。否则,将返回整个视图:

1return view('dashboard', ['users' => $users])
2 ->fragmentIf($request->hasHeader('HX-Request'), 'user-list');

fragmentsfragmentsIf 方法允许你在响应中返回多个视图片段。片段将被连接在一起。

1view('dashboard', ['users' => $users])
2 ->fragments(['user-list', 'comment-list']);
3 
4view('dashboard', ['users' => $users])
5 ->fragmentsIf(
6 $request->hasHeader('HX-Request'),
7 ['user-list', 'comment-list']
8 );

扩展 Blade

Blade 允许你使用 directive 方法定义自己的自定义指令。当 Blade 编译器遇到自定义指令时,它将调用提供的回调,并将该指令包含的表达式传递给它。

以下示例创建了一个 @datetime($var) 指令,它格式化给定的 $var,该变量应为 DateTime 的实例:

1<?php
2 
3namespace App\Providers;
4 
5use Illuminate\Support\Facades\Blade;
6use Illuminate\Support\ServiceProvider;
7 
8class AppServiceProvider extends ServiceProvider
9{
10 /**
11 * Register any application services.
12 */
13 public function register(): void
14 {
15 // ...
16 }
17 
18 /**
19 * Bootstrap any application services.
20 */
21 public function boot(): void
22 {
23 Blade::directive('datetime', function (string $expression) {
24 return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
25 });
26 }
27}

如你所见,我们将 format 方法链接到传递给指令的任何表达式上。因此,在此示例中,此指令生成的最终 PHP 代码将是:

1<?php echo ($var)->format('m/d/Y H:i'); ?>

在更新 Blade 指令的逻辑后,你需要删除所有缓存的 Blade 视图。可以使用 view:clear Artisan 命令删除缓存的 Blade 视图。

自定义 Echo 处理程序

如果你尝试使用 Blade “echo”一个对象,将调用该对象的 __toString 方法。__toString 方法是 PHP 内置的“魔术方法”之一。然而,有时你可能无法控制给定类的 __toString 方法,例如当你与之交互的类属于第三方库时。

在这些情况下,Blade 允许你为该特定类型的对象注册自定义 echo 处理程序。要实现这一点,你应该调用 Blade 的 stringable 方法。stringable 方法接受一个闭包。该闭包应该对它负责渲染的对象类型进行类型提示。通常,stringable 方法应该在你的应用程序的 AppServiceProvider 类的 boot 方法中调用:

1use Illuminate\Support\Facades\Blade;
2use Money\Money;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 Blade::stringable(function (Money $money) {
10 return $money->formatTo('en_GB');
11 });
12}

一旦定义了你的自定义 echo 处理程序,你就可以在 Blade 模板中简单地 echo 该对象了:

1Cost: {{ $money }}

自定义 If 语句

当定义简单的自定义条件语句时,编写自定义指令有时比必要的更复杂。因此,Blade 提供了一个 Blade::if 方法,它允许你使用闭包快速定义自定义条件指令。例如,让我们定义一个检查应用程序配置的默认“磁盘”的自定义条件。我们可以在 AppServiceProviderboot 方法中执行此操作:

1use Illuminate\Support\Facades\Blade;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Blade::if('disk', function (string $value) {
9 return config('filesystems.default') === $value;
10 });
11}

一旦定义了自定义条件,你就可以在模板中使用它:

1@disk('local')
2 <!-- The application is using the local disk... -->
3@elsedisk('s3')
4 <!-- The application is using the s3 disk... -->
5@else
6 <!-- The application is using some other disk... -->
7@enddisk
8 
9@unlessdisk('local')
10 <!-- The application is not using the local disk... -->
11@enddisk