Laravel 文档阅读:Blade 模版

周星驰电影作品《功夫》截图

简介

Blade 是 Laravel 提供的模版引擎,它简单、强大。不像其他的 PHP 模版引擎,Blade 允许在视图中使用原生 PHP 代码。实际上,所有的 Blade 视图最终都会被编译为原生 PHP 代码,缓存在 storage/framework/views 文件夹中。Laravel 使用的是这些编译后的缓存文件,而不是视图文件本身,所以,Blade 对于应用程序来说几乎是零开销的。当你修改了视图文件,那么它会重新编译并缓存,以便使用。Blade 视图文件以 .blade.php 作为后缀名,一般存储在 resources/views 文件夹中。

模版继承

定义布局文件

Blade 模版引擎的两个主要优点是 模版继承区块。举一个简单的例子,一个项目里,几乎所有的页面都是一样的布局,这时候就可以把这个布局提炼出来,作为“母版页”,继承了这个母版页的页面都有一样的布局效果,称为母版页的“子页”。母版页还叫布局文件,布局文件就是一个 Blade 视图:

<!-- Stored in 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 指令指定“继承”的布局文件,使用 @section 指令为在布局文件中使用 @section@yield 指令定义的地方注入内容。

<!-- Stored in resources/views/child.blade.php -->

@extends('layouts.app')

@section('title', 'Page Title')

@section('sidebar')

    <p>This is appended to the master sidebar.</p>
@endsection

@section('content')
    <p>This is my body content.</p>
@endsection

可以看到,在布局文件中使用 @yield 指令定义的地方,在子页中仍然使用 @section 注入内容;在布局文件中使用 @section 指令定义的一个好处是:在子页中使用 @section 注入内容时,可以使用 @ parent 指令附加(而非重写)在布局文件中的内容,而在布局文件中使用 @yield 指令定义的地方是做不到的。 @ parent 指令会在视图渲染时替换成布局文件里的内容。

注意,与在布局文件里定义的 sidebar 不同的是,子页里使用 @endsection 结束,而非 @show。因为 @endsection 仅用来定义区块,而 @show 是用来定义、立马产出区块的。

从路由中返回视图文件,要用到全局辅助函数 helper

Route::get('blade', function () {
    return view('child');
});

组件 & 插槽

组件和插槽提供了类似布局和区块的优点。而组件和插槽的心智模型更符合直觉。设想一下,在我们的项目中有一个可重用的“弹框”组件:

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

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

{{ $slot }} 变量表示插入的组件内容。构建此组件,是使用 Blade 的 @component 指令。

@component('alert')
    <strong>Whoops!</strong> Something went wrong!
@endcomponent

在这个场景里,{{ $slot }} 变量的内容就是

<strong>Whoops!</strong> Something went wrong!

有时一个组件需要有多个插槽。这时,只要稍许修改组件代码,定义一个“标题”插槽,这个插槽称命名插槽。命名插槽是通过简单地“打印”匹配其名称的变量来显示内容的:

<!-- /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 视图传递数据,是通过将变量包裹在大括号([])里实现的:

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

下面,就可以用 name 变量显示内容了:

Hello, {{ $name }}.

{{ }} 是 Blade 视图的打印语句,当然,打印语句里不限制只能打印变量内容,也可以使用 PHP 函数。实际上,打印语句这里可以使用任何 PHP 代码:

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

显示非转义数据

默认,所有传递给 Blade {{ }} 语句的内容都会使用 htmlspecialchars 函数处理、将内容转义,避免 XSS 攻击。如果无需转义输出的内容,可以使用下面的语法:

Hello, {!! $name !!}.

不过千万要小心,应该总是优先选择使用转义的 {{ }} 语法以避免 XSS 攻击。因为,有时你很能难避免用户有意的、无意的数据输入。

Blade & JavaScript 框架

由于一些 JavaScript 框架也使用花括号({{ }})语法解析内容,为了区分开 Blade 和这些用到的 JavaScript 框架,你可以使用 @ 符号来告诉 Blade 模版渲染引擎说,这个地方不要动,保持原样就可以了:

<h1>Laravel</h1>

Hello, @{{ name }}.

上面例子里,@ 符号会从 Blade 中删除,而 {{ name }} 会保持原样,用来给你的 JavaScript 框架渲染使用。

@verbatim 指令

如果使用 JavaScript 框架渲染的模版区域很大,这时就可以用 @verbatim 指令包裹这些模版区域,这样就避免了在每个 Blade 打印语句前都跟上 @ 符号的麻烦:

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

控制结构

除了模版继承和显示数据,Blade 还为常见的 PHP 控制结构提供了快捷方式,比如条件判断和循环。这些快捷方式提供了一种非常干净、简洁的控制结构,并且保持了原生 PHP 的形式。

If 语句

构造 if 语句使用 @if@elseif@elseendif 指令。这些指令和原生 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

除了讨论过的条件判断指令,Blade 还提供了 @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

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 里的对应功能相同。

@for ($i = 0; $i < 10; $i++)
    The current value is {{ $i }}
@endfor

[@foreach](https://learnku.com/users/5651) ($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](https://learnku.com/users/5651) ($users as $user)
    @if ($user->type == 1)
        @continue
    @endif

    <li>{{ $user->name }}</li>

    @if ($user->number == 5)
        @break
    @endif
@endforeach

也可以在一行完成这些操作:

[@foreach](https://learnku.com/users/5651) ($users as $user)
    @continue($user->type == 1)

    <li>{{ $user->name }}</li>

    @break($user->number == 5)
@endforeach

$loop 变量

循环时,循环内部有一个可用的变量 $loop。这个变量提供了跟循环有关的有用信息,比如当前迭代的索引、是否是第一次/最后依次迭代等:

[@foreach](https://learnku.com/users/5651) ($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

如果是在嵌套的循环里,就可以使用 $loop 变量的 parent 属性父级循环里的 $loop 变量:

[@foreach](https://learnku.com/users/5651) ($users as $user)
    [@foreach](https://learnku.com/users/5651) ($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 当前循环的嵌套水平(从 1 开始)。
$loop->parent 内层嵌套循环的父级 $loop 变量。

注释

Blade 的注释语法是 {{-- --}}。不像 HTMl 注释,Blade 模版注释不会出现在最终渲染的 HTML 代码里:

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

PHP

一些情况下,要在视图里嵌入 PHP 代码。这时可以用 [@php](https://learnku.com/users/10050) 指令在模版里执行一块原生 PHP 代码。

[@php](https://learnku.com/users/10050)
    //
@endphp

虽然 Blade 提供了此功能,但过于频繁的使用它,可能说明你在模版里嵌入太多逻辑代码了,这并不妥当。

引入子视图

Blade 允许在一个视图里通过 @include 指令引入一个视图。使用 @include 指令的视图称为父级视图,引入的视图称为子视图。父级视图里的所有变量在子视图里都是可以获得的:

<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'])

注意,不要使用 __DIR____FILE__ 常量引入 Blade 视图,因为程序实际使用的是编译后的、缓存在 storage/framework/views 文件夹中的文件。

为集合渲染视图

@each 指令整合了循环数据和引入视图的功能:

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

第一个参数是为数组或者集合中每个元素渲染数据时指定的视图,第二个参数是要遍历的数组或者集合,第三个参数是当前迭代的元素数据在子视图里的变量名。在上面的例子里,我们遍历的数组是 jobs,在 view.name 视图里渲染 job 变量,当前迭代的 key 使用 key 变量获取。

也可以为 @each 指令传递第四个参数,这是在给定数组元素为空时渲染的视图。

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

注意,使用 @each 指令渲染的视图不从父级模版里继承变量。如果子视图还需要这些变量,你应该使用 [@foreach](https://learnku.com/users/5651)@include 指令组合。

堆栈

Blade 允许你用 @push 指令向命名堆栈里推入内容,命名堆栈以 @stack 指令定义,可以定义在普通视图或者布局文件里,推入的内容会在视图或者布局文件里渲染出来。这在,为子视图添加额外的 JavaScript 库的场景下,特别有用。

<!--  在视图或者布局文件中定义堆栈 -->
<head>
    <!-- Head Contents -->

    @stack('scripts')
</head>

<!-- 在子视图中推入堆栈内容 -->
@push('scripts')
    <script src="/example.js"></script>
@endpush

你可以尽你所需的多次向堆栈推入数据。

服务注入

@inject 指令可从 Laravel 的服务提供者中获得服务。传递给 @inject 指令的第一个参数是一个变量名,获得的服务就是存放在这个变量里的,第二个参数就是你要解析的服务的类名或接口名。

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

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

扩展 Blade

Blade 允许你用 directive 方法创建自定义指令。当 Blade 解析遇到自定义指令时,会将接受到的表达式(expression)放在自定义指令的回调闭包里处理。

在下面的例子里,我们为创建了一个 @datatime($var) 指令,$var 应该是一个 DateTime 实例:

<?php

namespace App\Providers;

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

class AppServiceProvider extends ServiceProvider
{
    /**
     * Perform post-registration booting of services.
     *
     * @return void
     */
    public function boot()
    {
        Blade::directive('datetime', function ($expression) {
            return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
        });
    }

    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

可以看到,我们在传递过来的表达式上使用了 format 方法。在这个例子里,@datatime($var) 指令最终生成的 PHP 代码如下:

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

注意,在更新自定义指令后,需要删除所有的视图缓存文件,可以用 view:cache Artisan 命令实现。

自定义 If 语句

当自定义指令涉及简单的条件判断时,使用 Blade::directive 的方式可能会变得稍复杂些。为此,Blade 引入了 Blade::if 方法,使用闭包来快速自定义条件判断指令。例如,我们定义一个指令,判断当前的项目环境,可以选择在 AppServiceProviderboot 里做这件事情:

use Illuminate\Support\Facades\Blade;

/**
 * Perform post-registration booting of services.
 *
 * @return void
 */
public function boot()
{
    Blade::if('env', function ($environment) {
        return app()->environment($environment);
    });
}

定义好后,咱使用它:

@env('local')
    // The application is in the local environment...
@else
    // The application is not in the local environment...
@endenv

是不是很简单呢??

翻译、衍生自:https://learnku.com/docs/laravel/5.5/blade

图片来源:周星驰电影作品《功夫》。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 1

不错,学习了

6年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!