简介

Blade 是 Laravel 内置的一个简单而强大的模板引擎。与某些 PHP 模板引擎不同,Blade 不会限制你在模板中使用纯 PHP 代码。事实上,所有 Blade 模板都会被编译成纯 PHP 代码并进行缓存,直到被修改,这意味着 Blade 基本上不会给你的应用带来任何额外开销。Blade 模板文件使用 .blade.php 文件扩展名,通常存储在 resources/views 目录中。

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

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

使用 Livewire 为 Blade 赋能

想要将 Blade 模板提升到新水平,轻松构建动态界面?请了解 Laravel Livewire。Livewire 允许你编写带有动态功能的 Blade 组件,而这些动态功能通常只能通过 React、Svelte 或 Vue 等前端框架实现,它提供了一种绝佳的方式,能在不引入许多 JavaScript 框架的复杂性、客户端渲染和构建步骤的情况下,构建现代化、响应式的前端。

显示数据

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

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

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

Hello, {{ $name }}.

Blade 的 {{ }} 输出语句会自动经过 PHP 的 htmlspecialchars 函数处理,以防止 XSS 攻击。

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

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

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
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Blade::withoutDoubleEncoding();
    }
}

显示未转义数据

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

Hello, {!! $name !!}.

在输出由应用用户提供的内容时,请务必谨慎。在显示用户提供的数据时,通常应使用经过转义的双大括号语法,以防止 XSS 攻击。

Blade 与 JavaScript 框架

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

<h1>Laravel</h1>

Hello, @{{ name }}.

在此示例中,@ 符号会被 Blade 移除;但 {{ name }} 表达式会保持原样,不会被 Blade 引擎处理,从而允许 JavaScript 框架对其进行渲染。

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

{{-- Blade template --}}
@@if()

<!-- HTML output -->
@if()

渲染 JSON

有时你可能会向视图传递一个数组,目的是将其渲染为 JSON,以便初始化一个 JavaScript 变量。例如:

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

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

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

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

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

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

@verbatim 指令

如果你要在模板的大部分内容中显示 JavaScript 变量,可以使用 @verbatim 指令包裹 HTML,这样就不必在每个 Blade 输出语句前添加 @ 符号了:

@verbatim
    <div class="container">
        Hello, {{ name }}.
    </div>
@endverbatim

Blade 指令

除了模板继承和显示数据外,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 is defined and is not null...
@endisset

@empty($records)
    // $records is "empty"...
@endempty

身份验证指令

可以使用 @auth 和 @guest 指令快速判断当前用户是否已登录或是访客:

@auth
    // The user is authenticated...
@endauth

@guest
    // The user is not authenticated...
@endguest

如有需要,在使用 @auth 和 @guest 指令时,你可以指定要验证的身份验证守卫:

@auth('admin')
    // The user is authenticated...
@endauth

@guest('admin')
    // The user is not authenticated...
@endguest

环境指令

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

@production
    // Production specific content...
@endproduction

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

@env('staging')
    // The application is running in "staging"...
@endenv

@env(['staging', 'production'])
    // The application is running in "staging" or "production"...
@endenv

区块指令

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

@hasSection('navigation')
    <div class="pull-right">
        @yield('navigation')
    </div>

    <div class="clearfix"></div>
@endif

你可以使用 sectionMissing 指令判断某个区块是否没有内容:

@sectionMissing('navigation')
    <div class="pull-right">
        @include('default-navigation')
    </div>
@endif

会话指令

可以使用 @session 指令来判断是否存在 session 值。如果 session 值存在,@session 与 @endsession 指令之间的模板内容将被执行。在 @session 指令的内容中,可以使用 $value 变量来输出并显示 session 值:

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

上下文指令

可以使用 @context 指令来判断是否存在 context 值。如果 context 值存在,@context 与 @endcontext 指令之间的模板内容将被执行。在 @context 指令的内容中,可以使用 $value 变量来输出并显示 context 值:

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

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

在 foreach 循环中进行迭代时,可以使用 loop 变量来获取关于循环的有用信息,例如当前是否处于循环的第一次或最后一次迭代。

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

@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 循环中进行迭代时,循环内部会有一个 $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 the 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在嵌套循环中,父级循环的 loop 变量。

条件类名与样式

@class 指令会根据条件编译 CSS 类名字符串。该指令接受一个类名数组,其中数组的键表示希望添加的类名,而值是一个布尔表达式。如果数组元素使用数字键,则它始终会被包含在渲染后的类名列表中:

@php
    $isActive = false;
    $hasError = true;
@endphp

<span @class([
    'p-4',
    'font-bold' => $isActive,
    'text-gray-500' => ! $isActive,
    'bg-red' => $hasError,
])></span>

<span class="p-4 text-gray-500 bg-red"></span>

同样,可以使用 @style 指令根据条件为 HTML 元素添加内联 CSS 样式:

@php
    $isActive = true;
@endphp

<span @style([
    'background-color: red',
    'font-weight: bold' => $isActive,
])></span>

<span style="background-color: red; font-weight: bold;"></span>

附加属性

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

<input
    type="checkbox"
    name="active"
    value="active"
    @checked(old('active', $user->active))
/>

同样,可以使用 @selected 指令来指示给定的 select 选项是否应处于“selected”状态:

<select name="version">
    @foreach ($product->versions as $version)
        <option value="{{ $version }}" @selected(old('version') == $version)>
            {{ $version }}
        </option>
    @endforeach
</select>

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

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

另外,可以使用 @readonly 指令来指示给定的元素是否应处于“readonly”状态:

<input
    type="email"
    name="email"
    value="[email protected]"
    @readonly($user->isNotAdmin())
/>

此外,可以使用 @required 指令来指示给定的元素是否应处于“required”状态:

<input
    type="text"
    name="title"
    value="title"
    @required($user->isAdmin())
/>

包含子视图

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

Blade 的 @include 指令允许你在一个视图中包含另一个 Blade 视图。父视图中可用的所有变量,子视图同样可用:

<div>
    @include('shared.errors')

    <form>
        <!-- Form Contents -->
    </form>
</div>

尽管被包含的视图会继承父视图中所有可用的数据,但也可以传递一个额外的数据数组,以便在被包含的视图中使用:

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

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

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

如果希望在某个布尔表达式的计算结果为 true 或 false 时 @include 一个视图,可以使用 @includeWhen 和 @includeUnless 指令:

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

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

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

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

如果你想包含一个视图,且不继承父视图的任何变量,可以使用 @includeIsolated 指令。包含的视图只能访问你显式传递的变量:

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

应避免在 Blade 视图中使用 __DIR__ 和 __FILE__ 常量,因为它们指向的是缓存的已编译视图的位置。

为集合渲染视图

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

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

@each 指令的第一个参数是要为数组或集合中的每个元素渲染的视图。第二个参数是你要迭代的数组或集合,第三个参数是视图内分配给当前迭代的变量名。所以,举例来说,如果你正在迭代一个 jobs 数组,通常会希望在视图中将每个职位作为 job 变量来访问。当前迭代的数组键将作为视图中的 key 变量可用。

你也可以向 @each 指令传递第四个参数。该参数用于指定当给定数组为空时要渲染的视图。

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

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

@once 指令

@once 指令允许你定义模板的一部分,该部分在每个渲染周期内只会被求值一次。这对于使用栈将特定的一段 JavaScript 推送到页面头部很有用。例如,如果你在循环内渲染某个组件,你可能希望仅在该组件首次渲染时将 JavaScript 推送到头部:

@once
    @push('scripts')
        <script>
            // 你的自定义 JavaScript...
        </script>
    @endpush
@endonce

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

@pushOnce('scripts')
    <script>
        // 你的自定义 JavaScript...
    </script>
@endPushOnce

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

<!-- pie-chart.blade.php -->
@pushOnce('scripts', 'chart.js')
    <script src="/chart.js"></script>
@endPushOnce

<!-- line-chart.blade.php -->
@pushOnce('scripts', 'chart.js')
    <script src="/chart.js"></script>
@endPushOnce

原生 PHP

在某些情况下,在视图中嵌入 PHP 代码会很有用。你可以使用 Blade 的 @php 指令在模板中执行一段纯 PHP 代码:

@php
    $counter = 1;
@endphp

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

@use('App\Models\Flight')

你可以为 @use 指令提供第二个参数,以对导入的类进行别名处理:

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

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

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

@use 指令还支持通过在导入路径前添加 function 或 const 修饰符,来导入 PHP 函数和常量:

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

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

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

分组导入也同时支持 function 和 const 修饰符,允许你通过一条指令从同一个命名空间导入多个符号:

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

字体

当使用Laravel 的 Vite 字体优化功能时,你可以使用 @fonts 指令在应用程序的布局中渲染已配置的字体预加载链接以及内联字体 CSS:

<!doctype html>
<head>
    {{-- ... --}}

    @fonts
    @vite('resources/js/app.js')
</head>

@fonts 指令会渲染在 vite.config.js 文件中配置的所有字体族。通常应将该指令放置在应用程序根布局的 <head> 内,并位于使用这些字体的任何内容之前。

如果某个页面只需要部分已配置的字体,你可以向该指令传递一个或多个字体别名:

{{-- 加载单个字体别名…… --}}
@fonts('sans')

{{-- 加载多个字体别名…… --}}
@fonts(['sans', 'mono'])

字体别名是在 Vite 配置中定义字体时使用 alias 选项进行配置的。@fonts 指令会调用由 Vite facade 提供的 fonts 方法,该方法也可以直接调用:

{{ Vite::fonts(['sans', 'mono']) }}

注释

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

{{-- 此注释不会出现在渲染后的 HTML 中 --}}

组件

组件和插槽提供与区块、布局和包含相似的优势;不过,对于部分人而言,组件和插槽的概念模型更容易理解。编写组件有两种方式:基于类的组件和匿名组件。

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

php artisan make:component Alert

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

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

php artisan make:component Forms/Input

上述命令会在 app/View/Components/Forms 目录中创建 Input 组件,而视图则会被放置在 resources/views/components/forms 目录中。

手动注册包组件

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

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

use Illuminate\Support\Facades\Blade;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::component('package-alert', Alert::class);
}

一旦组件注册完成,就可以使用其标签别名来渲染它:

<x-package-alert/>

或者,你可以使用 componentNamespace 方法,按约定自动加载组件类。例如,一个 Nightshade 包可能包含位于 Nightshade\Views\Components 命名空间中的 Calendar 和 ColorPicker 组件:

use Illuminate\Support\Facades\Blade;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

这样一来,就可以使用 package-name:: 语法,通过供应商命名空间来使用包组件:

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

Blade 会通过将组件名转换为帕斯卡命名(pascal-case)的方式,自动检测与该组件关联的类。子目录也支持使用“点”符号表示。

渲染组件

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

<x-alert/>

<x-user-profile/>

如果组件类在 app/View/Components 目录中嵌套层级更深,你可以使用 . 字符来表示目录嵌套。例如,假设某组件位于 app/View/Components/Inputs/Button.php,则可按如下方式渲染它:

<x-inputs.button/>

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

use Illuminate\Support\Str;

/**
 * 组件是否应被渲染
 */
public function shouldRender(): bool
{
    return Str::length($this->message) > 0;
}

索引组件

有时组件属于一个组件组,你可能希望将相关组件分组到同一个目录下。例如,假设有一个“card”组件,其类结构如下:

App\View\Components\Card\Card
App\View\Components\Card\Header
App\View\Components\Card\Body

由于根 Card 组件嵌套在 Card 目录中,你可能以为需要通过 <x-card.card> 渲染该组件。然而,当组件的文件名与组件目录名称一致时,Laravel 会自动将该组件视为“根”组件,并允许你直接渲染组件而无需重复目录名称:

<x-card>
    <x-card.header>...</x-card.header>
    <x-card.body>...</x-card.body>
</x-card>

向组件传递数据

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

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

你应该在组件类的构造函数中定义组件的所有数据属性。组件上的所有公共属性都会自动可供组件的视图使用。无需在组件的 render 方法中将数据传递给视图:

<?php

namespace App\View\Components;

use Illuminate\View\Component;
use Illuminate\View\View;

class Alert extends Component
{
    /**
     * 创建组件实例。
     */
    public function __construct(
        public string $type,
        public string $message,
    ) {}

    /**
     * 获取代表该组件的视图/内容。
     */
    public function render(): View
    {
        return view('components.alert');
    }
}

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

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

命名规范

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

/**
 * 创建组件实例。
 */
public function __construct(
    public string $alertType,
) {}

可以将 $alertType 参数按如下方式提供给组件:

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

简写属性语法

在向组件传递属性时,你也可以使用“简写属性”语法。这通常很方便,因为属性名称通常与其对应的变量名称一致:

{{-- 简写属性语法... --}}
<x-profile :$userId :$name />

{{-- 等价于... --}}
<x-profile :user-id="$userId" :name="$name" />

转义属性渲染

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

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

Blade 将渲染如下 HTML:

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

组件方法

除了公共变量可供组件模板使用外,组件上的任何公共方法都可以被调用。例如,假设某组件具有 isSelected 方法:

/**
 * 判断给定的选项是否为当前选中的选项。
 */
public function isSelected(string $option): bool
{
    return $option === $this->selected;
}

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

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

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

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

use Closure;

/**
 * 获取代表该组件的视图/内容。
 */
public function render(): Closure
{
    return function () {
        return '<div {{ $attributes }}>Components content</div>';
    };
}

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

return function (array $data) {
    // $data['componentName'];
    // $data['attributes'];
    // $data['slot'];

    return '<div {{ $attributes }}>Components content</div>';
};

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

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

闭包应返回一个字符串。如果返回的字符串对应一个现有的视图,则将渲染该视图;否则,返回的字符串将作为内联 Blade 视图进行求值。

额外依赖

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

use App\Services\AlertCreator;

/**
 * 创建组件实例。
 */
public function __construct(
    public AlertCreator $creator,
    public string $type,
    public string $message,
) {}

隐藏属性/方法

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

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
    /**
     * 不应暴露给组件模板的属性/方法。
     *
     * @var array
     */
    protected $except = ['type'];

    /**
     * 创建组件实例。
     */
    public function __construct(
        public string $type,
    ) {}
}

组件属性

我们已经探讨了如何向组件传递数据属性;不过,有时你可能需要指定额外的 HTML 属性,例如 class,这些属性不属于组件运行所需的数据。通常,你需要将这些额外属性传递到组件模板的根元素上。例如,假设我们要按如下方式渲染一个 alert 组件:

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

所有不属于组件构造函数一部分的属性,都将自动添加到组件的“属性包”中。该属性包会通过 $attributes 变量自动提供给组件使用。通过输出该变量,即可在组件内部渲染所有属性:

<div {{ $attributes }}>
    <!-- 组件内容 -->
</div>

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

默认 / 合并属性

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

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

假设此组件的使用方式如下:

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

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

<div class="alert alert-error mb-4">
    <!-- $message 变量的内容 -->
</div>

条件合并类

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

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

如果需要合并其他属性到组件上,可以将 merge 方法链式调用到 class 方法之后:

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

如果需要在其他不应接收合并属性的 HTML 元素上对类进行条件编译,可以使用 @class 指令。

非类属性的合并

当合并的不是 class 属性时,传递给 merge 方法的值将被视为该属性的“默认”值。然而,与 class 属性不同,这些属性不会与注入的属性值进行合并,而是会被覆盖。例如,一个 button 组件的实现可能如下所示:

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

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

<x-button type="submit">
    提交
</x-button>

本示例中,button 组件渲染后的 HTML 如下:

<button type="submit">
    提交
</button>

如果希望某个非 class 属性的默认值与注入值合并在一起,可以使用 prepends 方法。在此示例中,data-controller 属性始终以 profile-controller 开头,任何额外注入的 data-controller 值都将置于该默认值之后:

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

获取与过滤属性

你可以使用 filter 方法来过滤属性。该方法接收一个闭包,若希望在属性包中保留某个属性,则该闭包应返回 true:

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

为方便起见,你可以使用 whereStartsWith 方法,来获取所有键以指定字符串开头的属性:

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

反之,可以使用 whereDoesntStartWith 方法来排除所有键以指定字符串开头的属性:

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

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

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

若要检查组件上是否存在某个属性,可以使用 has 方法。该方法仅接收属性名作为参数,并返回一个布尔值,用于指示该属性是否存在:

@if ($attributes->has('class'))
    <div>存在 class 属性</div>
@endif

如果向 has 方法传入一个数组,该方法将判断组件上是否存在所有给定的属性:

@if ($attributes->has(['name', 'class']))
    <div>所有属性均存在</div>
@endif

可以使用 hasAny 方法来判断组件上是否存在任意一个给定属性:

@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
    <div>存在其中某个属性</div>
@endif

可以使用 get 方法来获取指定属性的值:

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

可以使用 only 方法,仅获取具有指定键的属性:

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

可以使用 except 方法,获取除指定键之外的所有属性:

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

保留关键字

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

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

插槽(Slots)

你通常需要通过“插槽”向组件传递额外内容。组件插槽可通过输出 $slot 变量来进行渲染。为了理解这一概念,我们假设有一个 alert 组件,其标记如下:

<!-- /resources/views/components/alert.blade.php -->

<div class="alert alert-danger">
    {{ $slot }}
</div>

我们可以将内容注入组件,从而向 slot 传递内容:

<x-alert>
    <strong>哎呀!</strong> 出现了错误!
</x-alert>

有时,组件可能需要在不同位置渲染多个不同的插槽。我们来修改一下 alert 组件,使其支持注入“title”插槽:

<!-- /resources/views/components/alert.blade.php -->

<span class="alert-title">{{ $title }}</span>

<div class="alert alert-danger">
    {{ $slot }}
</div>

你可以使用 x-slot 标签来定义具名插槽的内容。任何未包含在显式 x-slot 标签中的内容,都会被传入组件的 $slot 变量:

<x-alert>
    <x-slot:title>
        服务器错误
    </x-slot>

    <strong>哎呀!</strong> 出现了错误!
</x-alert>

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

<span class="alert-title">{{ $title }}</span>

<div class="alert alert-danger">
    @if ($slot->isEmpty())
        如果插槽为空,此处为默认内容。
    @else
        {{ $slot }}
    @endif
</div>

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

@if ($slot->hasActualContent())
    该作用域存在非注释内容。
@endif

作用域插槽

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

<x-alert>
    <x-slot:title>
        {{ $component->formatAlert('Server Error') }}
    </x-slot>

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

插槽属性

与 Blade 组件类似,你可以为插槽分配附加属性,例如 CSS 类名:

<x-card class="shadow-sm">
    <x-slot:heading class="font-bold">
        Heading
    </x-slot>

    Content

    <x-slot:footer class="text-sm">
        Footer
    </x-slot>
</x-card>

若要与插槽属性进行交互,你可以访问插槽变量的 attributes 属性。若要了解更多与属性交互的方式,请参阅有关组件属性的文档:

@props([
    'heading',
    'footer',
])

<div {{ $attributes->class(['border']) }}>
    <h1 {{ $heading->attributes->class(['text-lg']) }}>
        {{ $heading }}
    </h1>

    {{ $slot }}

    <footer {{ $footer->attributes->class(['text-gray-700']) }}>
        {{ $footer }}
    </footer>
</div>

内联组件视图

对于非常小型的组件,同时管理组件类和组件的视图模板可能会显得繁琐。因此,你可以从 render 方法中直接返回组件的标记内容:

/**
 * Get the view / contents that represent the component.
 */
public function render(): string
{
    return <<<'blade'
        <div class="alert alert-danger">
            {{ $slot }}
        </div>
    blade;
}

生成内联视图组件

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

php artisan make:component Alert --inline

动态组件

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

// $componentName = "secondary-button";

<x-dynamic-component :component="$componentName" class="mt-4" />

手动注册组件

以下关于手动注册组件的文档主要适用于编写包含视图组件的 Laravel 包的开发者。如果你并非在编写包,组件文档的这部分内容可能与你无关。

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

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

use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::component('package-alert', AlertComponent::class);
}

组件注册完成后,就可以使用其标签别名进行渲染:

<x-package-alert/>

自动加载包组件

另外,你也可以使用 componentNamespace 方法按约定自动加载组件类。例如,一个 Nightshade 包可能包含位于 Nightshade\Views\Components 命名空间内的 Calendar 和 ColorPicker 组件:

use Illuminate\Support\Facades\Blade;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

这样你就可以使用 package-name:: 语法,通过供应商命名空间来使用包组件:

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

Blade 会通过将组件名称转换为帕斯卡命名法,自动检测与此组件关联的类。子目录也支持使用“点”表示法。

匿名组件

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

<x-alert/>

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

<x-inputs.button/>

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

php artisan make:component forms.input --view

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

匿名索引组件

有时,当一个组件由多个 Blade 模板组成时,你可能希望将该组件的模板归类到同一个目录中。例如,设想一个“手风琴(accordion)”组件,其目录结构如下:

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

这样的目录结构允许你像下面这样渲染手风琴组件及其项:

<x-accordion>
    <x-accordion.item>
        ...
    </x-accordion.item>
</x-accordion>

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

所幸的是,Blade 允许你在组件目录内放置一个与组件目录名称一致的文件。当此模板存在时,即便它嵌套在目录内,也可以作为组件的“根”元素被渲染。因此,我们可以继续使用上述示例中的相同 Blade 语法;不过,我们会按如下方式调整目录结构:

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

数据属性

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

你可以在组件 Blade 模板的开头使用 @props 指令,指定哪些属性应被视为数据变量。组件上的其余所有属性都将通过组件的属性包进行访问。如果你想为数据变量设置默认值,可以将变量名指定为数组的键,将默认值指定为数组的值:

<!-- /resources/views/components/alert.blade.php -->

@props(['type' => 'info', 'message'])

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

基于上述组件定义,我们可以像这样渲染该组件:

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

访问父级数据

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

<x-menu color="purple">
    <x-menu.item>...</x-menu.item>
    <x-menu.item>...</x-menu.item>
</x-menu>

<x-menu> 组件的实现可能如下所示:

<!-- /resources/views/components/menu/index.blade.php -->

@props(['color' => 'gray'])

<ul {{ $attributes->merge(['class' => 'bg-'.$color.'-200']) }}>
    {{ $slot }}
</ul>

由于 color 属性仅传递给了父组件(<x-menu>),因此在 <x-menu.item> 内部无法使用它。不过,如果使用 @aware 指令,我们也能让它在该组件内可用:

<!-- /resources/views/components/menu/item.blade.php -->

@aware(['color' => 'gray'])

<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
    {{ $slot }}
</li>

@aware 指令无法访问未通过 HTML 属性显式传递给父组件的父级数据。未显式传递给父组件的 @props 默认值,也无法被 @aware 指令访问。

匿名组件路径

正如之前所讨论的,匿名组件通常通过在 resources/views/components 目录中放置 Blade 模板来定义。不过,除了默认路径外,有时你可能还需要在 Laravel 中注册其他匿名组件路径。

anonymousComponentPath 方法的第一个参数接受匿名组件位置的“路径”,第二个参数是可选的组件所属“命名空间”。通常,该方法应在应用的某个 服务提供者的 boot 方法中调用:

/**
 * 初始化应用服务。
 */
public function boot(): void
{
    Blade::anonymousComponentPath(__DIR__.'/../components');
}

当注册组件路径时未指定前缀(如上例所示),在 Blade 组件中使用这些组件时也可以不带对应前缀。例如,若在上述注册路径中存在 panel.blade.php 组件,则可按如下方式渲染:

<x-panel />

前缀“命名空间”可作为第二个参数传入 anonymousComponentPath 方法:

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

提供前缀后,该“命名空间”内的组件在渲染时,需在组件名称前加上其命名空间作为前缀:

<x-dashboard::panel />

构建布局

使用组件构建布局

大多数 Web 应用在不同页面间会保持相同的通用布局。若在创建的每个视图中都重复编写整个布局的 HTML,将极其繁琐且难以维护。好在我们可以便捷地将该布局定义为单个 Blade 组件,并在应用中统一使用。

定义布局组件

例如,假设我们正在开发一个“待办事项”列表应用。我们可以定义一个如下所示的 layout 组件:

<!-- resources/views/components/layout.blade.php -->

<html>
    <head>
        <title>{{ $title ?? 'Todo Manager' }}</title>
    </head>
    <body>
        <h1>Todos</h1>
        <hr/>
        {{ $slot }}
    </body>
</html>

应用布局组件

定义好 layout 组件后,我们可以创建一个使用该组件的 Blade 视图。本例中,我们将定义一个用于显示任务列表的简单视图:

<!-- resources/views/tasks.blade.php -->

<x-layout>
    @foreach ($tasks as $task)
        <div>{{ $task }}</div>
    @endforeach
</x-layout>

请记住,注入到组件中的内容会传递到 layout 组件内的默认 $slot 变量。正如你可能注意到的,我们的 layout 组件还会遵循一个 $title 插槽(若有提供);否则将显示默认标题。我们可以按照组件文档中讨论的标准插槽语法,从任务列表视图中注入自定义标题:

<!-- resources/views/tasks.blade.php -->

<x-layout>
    <x-slot:title>
        自定义标题
    </x-slot>

    @foreach ($tasks as $task)
        <div>{{ $task }}</div>
    @endforeach
</x-layout>

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

use App\Models\Task;

Route::get('/tasks', function () {
    return view('tasks', ['tasks' => Task::all()]);
});

使用模板继承构建布局

定义布局

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

首先,我们来看一个简单示例。先来看一个页面布局。由于大多数 Web 应用在不同页面间会保持相同的通用布局,将该布局定义为单个 Blade 视图会很方便:

<!-- resources/views/layouts/app.blade.php -->

<html>
    <head>
        <title>App Name - @yield('title')</title>
    </head>
    <body>
        @section('sidebar')
            这是主侧边栏。
        @show

        <div class="container">
            @yield('content')
        </div>
    </body>
</html>

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

既然已经为应用定义了布局,接下来我们定义一个继承该布局的子页面。

扩展布局

定义子视图时,使用 @extends Blade 指令指定子视图应“继承”的布局。扩展 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', '默认内容')

表单

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 指令会编译为一个“if”语句,因此你可以使用 @else 指令,在属性不存在错误时渲染相应内容:

<!-- /resources/views/auth.blade.php -->

<label for="email">Email address</label>

<input
    id="email"
    type="email"
    class="@error('email') is-invalid @else is-valid @enderror"
/>

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

<!-- /resources/views/auth.blade.php -->

<label for="email">Email address</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 允许你将内容推送到命名栈中,这些内容可以在其他视图或布局的其他地方进行渲染。这对于指定子视图所需的任何 JavaScript 库尤为有用:

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

如果给定的布尔表达式求值为 true,并且你希望使用 @push 推送内容,可以使用 @pushIf 指令:

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

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

<head>
    <!-- Head Contents -->

    @stack('scripts')
</head>

@stack 指令会在执行到它时渲染此前已经推送的内容。之后推送的内容将不会被包含在内,例如从同一布局组件中、位于 @stack 指令下方渲染的组件所推送的内容。如果你需要从这类组件中推送内容,请将 @stack 指令置于这些组件之后,例如放在 <body> 标签的末尾。

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

@push('scripts')
    This will be second...
@endpush

// Later...

@prepend('scripts')
    This will be first...
@endprepend

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

@hasstack('list')
    <ul>
        @stack('list')
    </ul>
@endif

服务注入

@inject 指令可用于从 Laravel 服务容器中获取服务。传递给 @inject 的第一个参数是用于存储服务的变量名,第二个参数是你希望解析的服务的类名或接口名:

@inject('metrics', 'App\Services\MetricsService')

<div>
    Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
</div>

渲染内联 Blade 模板

有时,你可能需要将原始的 Blade 模板字符串转换为有效的 HTML。你可以通过 Blade 门面提供的 render 方法来实现这一点。render 方法接收 Blade 模板字符串和一个可选的数据数组,用于向模板传递数据:

use Illuminate\Support\Facades\Blade;

return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);

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

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

渲染 Blade 片段

当使用 Turbo 和 htmx 等前端框架时,有时你可能需要只在 HTTP 响应中返回 Blade 模板的一部分内容。Blade “片段” 就能让你实现这一点。首先,将部分 Blade 模板内容放入 @fragment 和 @endfragment 指令之间:

@fragment('user-list')
    <ul>
        @foreach ($users as $user)
            <li>{{ $user->name }}</li>
        @endforeach
    </ul>
@endfragment

然后,在渲染使用该模板的视图时,你可以调用 fragment 方法,指定仅将指定的片段包含在返回的 HTTP 响应中:

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

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

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

fragments 和 fragmentsIf 方法允许你在响应中返回多个视图片段。这些片段将被拼接在一起:

view('dashboard', ['users' => $users])
    ->fragments(['user-list', 'comment-list']);

view('dashboard', ['users' => $users])
    ->fragmentsIf(
        $request->hasHeader('HX-Request'),
        ['user-list', 'comment-list']
    );

扩展 Blade

Blade 允许你使用 directive 方法定义自定义指令。当 Blade 编译器遇到自定义指令时,会调用提供的回调函数,并传入该指令所包含的表达式。

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

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Blade::directive('datetime', function (string $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 视图。

自定义输出处理

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

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

use Illuminate\Support\Facades\Blade;
use Money\Money;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Blade::stringable(function (Money $money) {
        return $money->formatTo('en_GB');
    });
}

定义好自定义输出处理程序后,你可以在 Blade 模板中直接输出该对象:

Cost: {{ $money }}

自定义条件语句

在定义简单、自定义的条件语句时,编写自定义指令有时会过于复杂。因此,Blade 提供了 Blade::if 方法,让你可以使用闭包快速定义自定义条件指令。例如,我们来定义一个自定义条件,用于检查应用程序配置的默认“磁盘”。我们可以在 AppServiceProvider 的 boot 方法中进行定义:

use Illuminate\Support\Facades\Blade;

/**
 * 初始化任何应用程序服务。
 */
public function boot(): void
{
    Blade::if('disk', function (string $value) {
        return config('filesystems.default') === $value;
    });
}

自定义条件定义完成后,可在模板中使用它:

@disk('local')
    <!-- 应用程序正在使用本地磁盘…… -->
@elsedisk('s3')
    <!-- 应用程序正在使用 s3 磁盘…… -->
@else
    <!-- 应用程序正在使用其他磁盘…… -->
@enddisk

@unlessdisk('local')
    <!-- 应用程序未使用本地磁盘…… -->
@enddisk