hyperf/docs/zh-tw/view-engine.md
2021-07-19 13:00:24 +08:00

24 KiB
Raw Blame History

檢視引擎

基於 Laravel blade 模板引擎改寫, 支援原始 blade 模板引擎的語法.

composer require hyperf/view-engine

生成配置

php bin/hyperf.php vendor:publish hyperf/view-engine

預設配置如下

本元件推薦使用 SYNC 的渲染模式,可以有效減少程序間通訊的損耗

return [
    'engine' => Hyperf\ViewEngine\HyperfViewEngine::class,
    'mode' => Hyperf\View\Mode::SYNC,
    'config' => [
        'view_path' => BASE_PATH . '/storage/view/',
        'cache_path' => BASE_PATH . '/runtime/view/',
    ],

    # 自定義元件註冊
    'components' => [
        // 'alert' => \App\View\Components\Alert::class
    ],

    # 檢視名稱空間 (主要用於擴充套件包中)
    'namespaces' => [
        // 'admin' => BASE_PATH . '/storage/view/vendor/admin',
    ],
];

使用

本使用教程大量借鑑於 LearnKu,十分感謝 LearnKu 對 PHP 社群做出的貢獻。

簡介

BladeLaravel 提供的一個簡單而又強大的模板引擎。和其他流行的 PHP 模板引擎不同,Blade 並不限制你在檢視中使用原生 PHP 程式碼。 所有 Blade 檢視檔案都將被編譯成原生的 PHP 程式碼並快取起來,除非它被修改,否則不會重新編譯,這就意味著 Blade 基本上不會給你的應用增加任何負擔。 Blade 檢視檔案使用 .blade.php 作為副檔名,預設被存放在 storage/view 目錄。

模板繼承

定義佈局

首先,我們來研究一個「主」頁面佈局。因為大多數 web 應用會在不同的頁面中使用相同的佈局方式,因此可以很方便地定義單個 Blade 佈局檢視:

<!-- Stored in storage/view/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 的意思,一個片段,@section 指令定義了片段的內容,而 @yield 指令則用來顯示片段的內容。

現在,我們已經定義好了這個應用程式的佈局,接下來,我們定義一個繼承此佈局的子頁面。

佈局繼承

在定義一個子檢視時,使用 Blade@extends 指令指定子檢視要「繼承」的檢視。擴充套件自 Blade 佈局的檢視可以使用 @section 指令向佈局片段注入內容。 就如前面的示例中所示,這些片段的內容將由佈局中的 @yield 指令控制顯示:

<!-- Stored in storage/view/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 指令將被佈局中的內容替換。

和上一個示例相反,這裡的 sidebar 片段使用 @endsection 代替 @show 來結尾。 @endsection 指令僅定義了一個片段, @show 則在定義的同時 立即 yield 這個片段。

@yield 指令還接受一個預設值作為第二個引數。如果被「yield」的片段未定義則該預設值被渲染

@yield('content', 'Hyperf')

Blade 檢視可以用 Hyperf\ViewEngine\view 輔助函式返回:

<?php

declare(strict_types=1);

namespace App\Controller;

use Hyperf\HttpServer\Annotation\AutoController;
use function Hyperf\ViewEngine\view;

/**
 * @AutoController(prefix="view")
 */
class ViewController extends AbstractController
{
    public function child()
    {
        return (string) view('child');
    }
}

顯示資料

你可以把變數置於花括號中以在檢視中顯示資料。 例如,給定下方的路由:

use Hyperf\HttpServer\Router\Router;
use function Hyperf\ViewEngine\view;

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

您可以像如下這樣顯示 name 變數的內容:

Hello, {{ $name }}.

Blade 的 {{ }} 語句將被 PHP 的 htmlspecialchars 函式自動轉義以防範 XSS 攻擊。

不僅僅可以顯示傳遞給檢視的變數的內容,您亦可輸出任何 PHP 函式的結果。事實上,您可以在 Blade 模板的回顯語句放置任何 PHP 程式碼:

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

顯示非轉義字元

預設情況下,Blade {{ }} 語句將被 PHPhtmlspecialchars 函式自動轉義以防範 XSS 攻擊。如果您不想您的資料被轉義,那麼您可使用如下的語法:

Hello, {!! $name !!}.

在應用中顯示使用者提供的資料時請格外小心,請儘可能的使用轉義和雙引號語法來防範 XSS 攻擊。

渲染 JSON

有時,為了初始化一個 JavaScript 變數,您可能會向檢視傳遞一個數組並將其渲染成 JSON。例如:

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

當然,您亦可使用 @json Blade 指令來代替手動呼叫 json_encode 方法。@json 指令的引數和 PHPjson_encode 函式一致:

<script>
    var app = @json($array);

    var app = @json($array, JSON_PRETTY_PRINT);
</script>

使用 @json 指令時,您應該只渲染已經存在的變數為 JSON。Blade 模板是基於正則表示式的,如果嘗試將一個複雜表示式傳遞給 @json 指令可能會導致無法預測的錯誤。

HTML 實體編碼

預設情況下,Blade 將會對 HTML 實體進行雙重編碼。如果您想要禁用此舉,您可以監聽 BootApplication 事件,並呼叫 Blade::withoutDoubleEncoding 方法:

<?php

declare(strict_types=1);

namespace App\Listener;

use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Framework\Event\BootApplication;
use Hyperf\ViewEngine\Blade;
use Psr\Container\ContainerInterface;

/**
 * @Listener
 */
class BladeWithoutDoubleEncodingListener implements ListenerInterface
{
    /**
     * @var ContainerInterface
     */
    private $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function listen(): array
    {
        return [
            BootApplication::class
        ];
    }

    public function process(object $event)
    {
        Blade::withoutDoubleEncoding();
    }
}

Blade & JavaScript 框架

由於許多 JavaScript 框架也使用「花括號」來標識將顯示在瀏覽器中的表示式,因此,您可以使用 @ 符號來表示 Blade 渲染引擎應當保持不便。例如:

<h1>Laravel</h1>

Hello, @{{ name }}.

在這個例子中,@ 符號將被 Blade 移除;當然,Blade 將不會修改 {{ name }} 表示式,取而代之的是 JavaScript 模板來對其進行渲染。 @ 符號也用於轉義 Blade 指令:

{{-- Blade --}}
@@json()

<!-- HTML 輸出 -->
@json()

如果您在模板中顯示很大一部分 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 (is_signed_in())
    You are not signed in.
@endunless

除了已經討論過了的條件指令外,@isset@empty 指令亦可作為它們所對應的 PHP 函式的快捷方式:

@isset($records)
    // $records 已經定義但不為空
@endisset

@empty($records)
    // $records 為空……
@endempty

區塊指令

您可以使用 @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

環境指令

您可以使用 @production 指令來判斷應用是否處於生產環境:

@production
    // 生產環境特定內容……
@endproduction

或者,您可以使用 @env 指令來判斷應用是否運行於指定的環境:

@env('staging')
    // 應用運行於「staging」環境……
@endenv

@env(['staging', 'production'])
    // 應用運行於 「staging」環境或生產環境……
@endenv

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 ($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 變數

迴圈時,迴圈內部可以使用 $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

如果您在巢狀迴圈中,您可以使用迴圈的 $loop 的變數的 parent 屬性訪問父級迴圈:

@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->even 當前迴圈的迭代次數是否是偶數。
$loop->odd 當前迴圈的迭代次數是否是奇數。
$loop->depth 當前迴圈的巢狀深度。
$loop->parent 巢狀迴圈中的父級迴圈。

註釋

Blade 也允許您在檢視中定義註釋。但是和 HTML 註釋不同, Blade 註釋不會被包含在應用返回的 HTML 中:

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

PHP

在許多情況下,嵌入 PHP 程式碼到您的檢視中是很有用的。您可以在模板中使用 Blade@php 指令執行原生的 PHP 程式碼塊:

@php
    //
@endphp

儘管 Blade 提供了這個功能,頻繁使用它可能使得您的模板中嵌入過多的邏輯。

@once 指令

@once 指令允許您定義模板的一部分內容,這部分內容在每一個渲染週期中只會被計算一次。 該指令在使用 堆疊 推送一段特定的 JavaScript 程式碼到頁面的頭部環境下是很有用的。 例如,如果您想要在迴圈中渲染一個特定的 元件,您可能希望僅在元件渲染的首次推送 JavaScript 程式碼到頭部:

@once
    @push('scripts')
        <script>
            // 您自定義的 JavaScript 程式碼
        </script>
    @endpush
@endonce

元件及插槽

元件和插槽的作用與片段Section和佈局Layout類似。不過有些人可能認為元件和插槽使用起來更加方便。Hyperf 支援兩種編寫元件的方法:基於類的元件和匿名元件。

我們可以通過建立一個繼承 \Hyperf\ViewEngine\Component\Component::class 類的 class 來定義一個類元件。以下將通過建立一個簡單的 Alert 元件來向你說明如何使用元件。

app/View/Component/Alert.php

<?php
namespace App\View\Component;
use Hyperf\ViewEngine\Component\Component;
use function Hyperf\ViewEngine\view;
class Alert extends Component
{
    public $type;
    public $message;
    public function __construct($type, $message)
    {
        $this->type = $type;
        $this->message = $message;
    }
    public function render()
    {
        return view('components.alert');
    }
}

storage/view/components/alert.blade.php

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

手動註冊元件

config/autoload/view.php

<?php
return [
    // ...
    'components' => [
        'alert' => \App\View\Component\Alert::class,
    ],
];

或者在擴充套件包中的 ConfigProvider

<?php
class ConfigProvider
{
    public function __invoke()
    {
        return [
            // ...others config
            'view' => [
                // ...others config
                'components' => [
                    'package-alert' => \App\View\Component\Alert::class,
                ],
            ],
        ];
    }
}

註冊元件後,你將可以通過 HTML 標籤別名來使用它:

<x-alert/>
<x-package-alert/>

顯示元件

你可以在任一 Blade 模板中使用 Blade 元件標籤來顯示元件。Blade 元件標籤以 x- 開頭,後面接上元件的名稱。

<x-alert/>
<x-package-alert/>

元件傳參

你可以使用 HTML 屬性將資料傳遞給 Blade 元件。普通的值可以通過簡單的 HTML 屬性傳遞,而 PHP 表示式及變數應當通過以 : 為字首的屬性傳遞:

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

!> 注意:你可以在元件類的建構函式中定義元件所需的資料。元件類中的所有公共屬性都將自動傳遞給元件檢視。不必通過元件類的 render 方法傳遞。渲染元件時,可以通過變數名稱來獲取元件類公共屬性的內容。

元件方法

除了可獲取元件類的公共屬性外,還可以在元件檢視中執行元件類上的任何公共方法。例如,某元件具有一個 isSelected 方法:

    /**
     * 判斷給定選項是否為當前選項
     *
     * @param  string  $option
     * @return bool
     */
    public function isSelected($option)
    {
        return $option === $this->selected;
    }

你可以通過呼叫與方法名稱相同的變數來執行該方法:

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

附加依賴項

如果你的元件需要依賴其他的類,則應當在元件所有資料屬性之前列出它們,它們將會被容器自動注入:

    use App\AlertCreator;
    /**
     * 建立元件例項
     *
     * @param  \App\AlertCreator  $creator
     * @param  string  $type
     * @param  string  $message
     * @return void
     */
    public function __construct(AlertCreator $creator, $type, $message)
    {
        $this->creator = $creator;
        $this->type = $type;
        $this->message = $message;
    }

管理屬性

我們已經瞭解瞭如何將資料屬性傳遞給元件。然而,有時候我們可能需要指定其他的 HTML 屬性(如 class),這些屬性不是元件所需要的資料。這種情況下,我們將會想要將這些屬性向下傳遞到元件模板的根元素。例如,我們要渲染一個 alert 元件,如下所示:

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

所有不屬於元件建構函式的屬性都將自動新增到元件的「屬性包」中。該屬性包將會通過 $attributes 變數傳遞給元件檢視。通過輸出此變數,即可在元件中呈現所有屬性:

    <div {{ $attributes }}>
        <!-- 元件內容 -->
    </div>

獲取屬性

您可以使用 get() 方法獲取特定的屬性值。此方法接受屬性名稱作為第一個引數(第二個引數為預設值),並將返回其值。

    <div class="{{ $attributes->get("class", "default") }}">
        <!-- 元件內容 -->
    </div>

檢測屬性

您可以使用 has() 方法獲取特定的屬性值。此方法接受屬性名稱作為引數,並將返回布林值。

    @if($attributes->has("class"))
        <div class="{{ $attributes->get("class") }}">
            <!-- 元件內容 -->
        </div>
    @endif

合併屬性

某些時候,你可能需要指定屬性的預設值,或將其他值合併到元件的某些屬性中。為此,你可以使用屬性包的 merge 方法:

    <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>

預設情況下,只會合併 class 屬性,其他屬性將會直接進行覆蓋,會出現如下情況:

// 定義
<div {{ $attributes->merge(['class' => 'alert alert-'.$type, 'other-attr' => 'foo']) }}>{{ $message }}</div>
// 使用
<x-alert type="error" :message="$message" class="mb-4" other-attr="bar"/>
// 呈現
<div class="alert alert-error mb-4" other-attr="bar"><!-- $message 變數的內容 --></div>

如上述情況,需要將 other-attr 屬性也合併的話,可以使用以下方式,在 merge() 方法中新增第二個引數 true

// 定義
<div {{ $attributes->merge(['class' => 'alert alert-'.$type, 'other-attr' => 'foo'], true) }}>{{ $message }}</div>
// 使用
<x-alert type="error" :message="$message" class="mb-4" other-attr="bar"/>
// 呈現
<div class="alert alert-error mb-4" other-attr="foo bar"><!-- $message 變數的內容 --></div>

插槽

通常,你需要通過 slots 向元件傳遞附加內容。 假設我們建立的 alert 元件具有以下標記:

    <!-- /storage/view/components/alert.blade.php -->

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

我門可以通過向元件注入內容的方式,將內容傳遞到 slots

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

有時候一個元件可能需要在它內部的不同位置放置多個不同的插槽。我們來修改一下 alert 元件,使其允許注入 title

    <!-- /storage/view/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 name="title">
            Server Error
        </x-slot>

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

內聯元件

對於小型元件而言,管理元件類和元件檢視模板可能會很麻煩。因此,您可以從 render 方法中返回元件的內容:

    public function render()
    {
        return <<<'blade'
            <div class="alert alert-danger">
                {{ $slot }}
            </div>
        blade;
    }

匿名元件

與行內元件相同,匿名元件提供了一個通過單個檔案管理元件的機制。然而,匿名元件使用的是一個沒有關聯類的單一檢視檔案。要定義一個匿名元件,您只需將 Blade 模板置於 /storage/view/components 目錄下。 例如,假設您在 /storage/view/components/alert.blade.php 中定義了一個元件:

    <x-alert/>

如果元件在 components 目錄的子目錄中,您可以使用 . 字元來指定其路徑。例如,假設元件被定義在 /storage/view/components/inputs/button.blade.php 中,您可以像這樣渲染它:

    <x-inputs.button/>

匿名元件資料及屬性

由於匿名元件沒有任何關聯類,您可能想要區分哪些資料應該被作為變數傳遞給元件,而哪些屬性應該被存放於 屬性包 中。

您可以在元件的 Blade 模板的頂層使用 @props 指令來指定哪些屬性應該作為資料變數。元件中的其他屬性都將通過屬性包的形式提供。如果您想要為某個資料變數指定一個預設值,您可以將屬性名作為陣列鍵,預設值作為陣列值來實現:

    <!-- /storage/view/components/alert.blade.php -->

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

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

動態元件

有時,您可能需要渲染一個元件,但在執行前不知道要渲染哪一個。這種情況下,您可以使用內建的 dynamic-component 元件來渲染一個基於值或變數的元件:

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

元件自動載入

預設情況下,App\View\Component\components. 下的元件會自動註冊。你也可以通過配置檔案修改這個配置:

config/autoload/view.php

return [
    // ...
    'autoload' => [
        'classes' => ['App\\Other\\Component\\', 'App\\Another\\Component\\'],
        'components' => ['package::components.', 'components.'],
    ],
];

檢視空間

通過定義檢視空間,可以方便的在你的擴充套件包中使用檢視檔案,只需要在 ConfigProvider 中新增一行配置即可:

<?php
class ConfigProvider
{
    public function __invoke()
    {
        return [
            // ...others config
            'view' => [
                // ...others config
                'namespaces' => [
                    'package-name' => __DIR__ . '/../views',
                ],
            ],
        ];
    }
}

在安裝擴充套件包之後,可以通過在專案的 /storage/view/vendor/package-name 中定義相同路徑的檢視檔案,來覆蓋擴充套件包中的檢視。

可選中介軟體

  • Hyperf\ViewEngine\Http\Middleware\ShareErrorsFromSession::class

自動將 session 中的 errors 共享給檢視,依賴 hyperf/session 元件

  • Hyperf\ViewEngine\Http\Middleware\ValidationExceptionHandle::class

自動捕捉 validation 中的異常加入到 session 中,依賴 hyperf/sessionhyperf/validation 元件

其他命令

自動安裝 view-enginetranslationvalidation 元件相關配置

php bin/hyperf.php view:publish