hyperf/docs/en/view-engine.md
2021-06-26 17:57:43 +08:00

27 KiB
Raw Blame History

View engine

Rewritten based on the laravel blade template engine, supporting the syntax of the original blade template engine.

composer require hyperf/view-engine

Generate configuration

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

The default configuration is as follows

This component recommends using the SYNC rendering mode, which can effectively reduce the loss of inter-process communication

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

    # Custom component registration
    'components' => [
        //'alert' => \App\View\Components\Alert::class
    ],

    # View namespace (mainly used in extension packages)
    'namespaces' => [
        //'admin' => BASE_PATH.'/storage/view/vendor/admin',
    ],
];

Use

This tutorial is borrowed heavily from LearnKu, and I am very grateful to LearnKu for its contribution to the PHP community.

Introduction

Blade is a simple and powerful template engine provided by Laravel. Unlike other popular PHP template engines, Blade does not restrict you from using native PHP code in views. All Blade view files will be compiled into native PHP code and cached, unless it is modified, otherwise it will not be recompiled, which means that Blade basically does not add any burden to your application . The Blade view file uses .blade.php as the file extension and is stored in the storage/view directory by default.

Template inheritance

Define the layout

First, let's study a "main" page layout. Because most web applications will use the same layout on different pages, it is easy to define a single Blade layout view:

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

As you can see, this program contains common HTML. But please pay attention to @section and @yield and instructions. Just like the meaning of section, for a section, the @section directive defines the content of the section, and the @yield directive is used to display the content of the section.

Now that we have defined the layout of this application, next, we define a subpage that inherits this layout.

Layout inheritance

When defining a subview, use the @extends directive of Blade to specify the view that the subview should "inherit". Views that extend from the Blade layout can use the @section directive to inject content into the layout section. As shown in the previous example, the content of these fragments will be controlled and displayed by the @yield directive in the layout:

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

In this example, the sidebar fragment uses the @parent directive to append (not overwrite) content to the sidebar of the layout. When rendering the view, the @parent directive will be replaced by the content in the layout.

Contrary to the previous example, the sidebar fragment here ends with @endsection instead of @show. The @endsection directive defines only one section, while @show immediately yields this section while defining it.

The @yield command also accepts a default value as the second parameter. If the "yield" fragment is not defined, the default value is rendered:

@yield('content','Hyperf')

The Blade view can be returned by the Hyperf\ViewEngine\view helper function:

<?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');
    }
}

Displaying data

You can put variables in curly braces to display data in the view. For example, given the following route:

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

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

You can display the contents of the name variable as follows:

Hello, {{ $name }}.

Blade's {{ }} statement will be automatically escaped by PHP's htmlspecialchars function to prevent XSS attacks.

Not only can you display the contents of the variables passed to the view, you can also output the result of any PHP function. In fact, you can put any PHP code in the echo statement of the Blade template:

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

Display non-escaped characters

By default, Blade {{ }} statements will be automatically escaped by PHP's htmlspecialchars function to prevent XSS attacks. If you don't want your data to be escaped, you can use the following syntax:

Hello, {!! $name !!}.

Please be very careful when displaying user-supplied data in the app. Use escaping and double-quotation syntax as much as possible to prevent XSS attacks.

Render JSON

Sometimes, in order to initialize a JavaScript variable, you may pass an array to the view and render it as JSON. E.g:

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

Of course, you can also use @json Blade command instead of manually calling the json_encode method. The parameters of the @json instruction are the same as the json_encode function of PHP:

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

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

When using the @json directive, you should only render existing variables as JSON. The Blade template is based on regular expressions. Trying to pass a complex expression to the @json directive may cause unpredictable errors.

HTML entity encoding

By default, Blade will double-encode HTML entities. If you want to disable this, you can listen to the BootApplication event and call the Blade::withoutDoubleEncoding method:

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

Since many JavaScript frameworks also use "curly braces" to identify expressions that will be displayed in the browser, you can use the @ symbol to indicate that the Blade rendering engine should be inconvenient. E.g:

<h1>Laravel</h1>

Hello, @{{ name }}.

In this example, the @ symbol will be removed by Blade; of course, Blade will not modify the {{ name }} expression, instead JavaScript template to render it. The @ symbol is also used to escape the Blade instruction:

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

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

If you display a large part of the JavaScript variables in the template, you can embed HTML in the @verbatim directive, so that you dont need to add the @ symbol before every Blade echo statement :

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

Process Control

In addition to template inheritance and display data, Blade also provides convenient shortcuts for common PHP control structures, such as conditional statements and loops. These shortcuts provide a very clear and concise way of writing the PHP control structure. At the same time, it also maintains the similar grammatical characteristics to the control structure in PHP.

If statement

You can use @if, @elseif, @else and @endif directives to construct if statements. The functions of these commands are exactly the same as their corresponding PHP statements:

@if (count($records) === 1)
    I have one record!
@elseif (count($records)> 1)
    I have multiple records!
@else
    I don't have any records!
@endif

For convenience, Blade also provides a @unless instruction:

@unless (is_signed_in())
    You are not signed in.
@endunless

In addition to the conditional instructions already discussed, the @isset and @empty instructions can also be used as shortcuts to their corresponding PHP functions:

@isset($records)
    // $records has been defined but not empty
@endisset

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

Block instructions

You can use the @hasSection command to determine whether the block contains content:

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

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

You can use the @sectionMissing command to determine whether the block has no content:

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

Environmental Directives

You can use the @production command to determine whether the application is in a production environment:

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

Or, you can use the @env command to determine whether the application is running in a specified environment:

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

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

Switch statement

You can use @switch, @case, @break, @default and @endswitch statements to construct a Switch statement:

@switch($i)
    @case(1)
        First case...
        @break

    @case(2)
        Second case...
        @break

    @default
        Default case...
@endswitch

Loop

In addition to conditional statements, Blade also provides instructions with the same functions as the loop structure of PHP. Similarly, the functions of these statements are consistent with their corresponding PHP syntax:

@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

When looping, you can use loop variables to get valuable information about the loop, for example, you are in the first iteration or the last iteration of the loop.

When using a loop, you can terminate the loop or skip the current iteration:

@foreach ($users as $user)
    @if ($user->type == 1)
        @continue
    @endif

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

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

You can declare a conditional statement on a single line of the instruction:

@foreach ($users as $user)
    @continue($user->type == 1)

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

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

Loop variable

When looping, the $loop variable can be used inside the loop. This variable provides a way to access some information such as the current loop index and whether this iteration is the first or last time:

@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

If you are in a nested loop, you can access the parent loop using the parent property of the loop's $loop variable:

@foreach ($users as $user)
    @foreach ($user->posts as $post)
        @if ($loop->parent->first)
            This is first iteration of the parent loop.
        @endif
    @endforeach
@endforeach

The $loop variable also contains various useful attributes:

Properties Remarks
$loop->index The index of the current iteration (starting from 0).
$loop->iteration The number of iterations of the current loop (starting from 1).
$loop->remaining The number of remaining iterations of the loop.
$loop->count The number of elements in the array to be iterated.
$loop->first Whether the current iteration is the first iteration of the loop.
$loop->last Whether the current iteration is the last iteration of the loop.
$loop->even Whether the number of iterations of the current loop is even.
$loop->odd Whether the number of iterations of the current loop is odd.
$loop->depth The nesting depth of the current loop.
$loop->parent The parent loop in the nested loop.

Comment

Blade also allows you to define comments in the view. But unlike HTML comments, Blade comments will not be included in the HTML returned by the application:

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

PHP

In many cases, it is useful to embed PHP code in your view. You can use the @php instruction of Blade in the template to execute the native PHP code block:

@php
    //
@endphp

Although Blade provides this feature, frequent use of it may cause too much logic to be embedded in your templates.

@once directive

The @once directive allows you to define part of the template content, which will only be calculated once in each rendering cycle. This instruction is very useful in the context of using the stack to push a specific JavaScript code to the head of the page. For example, if you want to render a specific component in a loop, you may want to push the JavaScript code to the head only the first time the component is rendered:

@once
    @push('scripts')
        <script>
            // Your custom JavaScript code
        </script>
    @endpush
@endonce

Components and slots

The role of components and slots is similar to that of Section and Layout. However, some people may think that components and slots are more convenient to use. Hyperf supports two methods of writing components: class-based components and anonymous components.

We can define a class component by creating a class that inherits the \Hyperf\ViewEngine\Component\Component::class class. The following will show you how to use the component by creating a simple Alert component.

app/View/Compoent/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()
    {
        retnurn view('components.alert');
    }
}

storage/view/components/alert.blade.php

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

Manually register components

In config/autoload/view.php

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

Or in the ConfigProvider in the extension package

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

After registering the component, you will be able to use it through HTML tag aliases:

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

Display components

You can use Blade component tags in any Blade template to display components. The Blade component label starts with x-, followed by the name of the component.

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

Component parameter transfer

You can use HTML attributes to pass data to the Blade component. Ordinary values can be passed through simple HTML attributes, while PHP expressions and variables should be passed through attributes prefixed with ::

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

!> Note: You can define the data required by the component in the constructor of the component class. All public properties in the component class will be automatically passed to the component view. It does not have to be passed through the render method of the component class. When rendering a component, you can get the content of the public properties of the component class through the variable name.

Component method

In addition to obtaining the public properties of the component class, you can also execute any public methods on the component class in the component view. For example, a component has an isSelected method:

    /**
     * Determine whether the given option is the current option
     *
     * @param string $option
     * @return bool
     */
    public function isSelected($option)
    {
        return $option === $this->selected;
    }

You can execute the method by calling the variable with the same name as the method:

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

Additional dependencies

If your component needs to depend on other classes, you should list them before all the data attributes of the component, and they will be automatically injected by the container:

    use App\AlertCreator;
    /**
     * Create component instance
     *
     * @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;
    }

Manage properties

We have seen how to pass data attributes to components. However, sometimes we may need to specify other HTML attributes (such as class), which are not the data required by the component. In this case, we will want to pass these attributes down to the root element of the component template. For example, we want to render an alert component as follows:

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

All properties that are not part of the component's constructor will be automatically added to the component's "property bag". The attribute package will be passed to the component view via the $attributes variable. By outputting this variable, all properties can be rendered in the component:

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

Get attributes

You can use the get() method to get a specific attribute value. This method accepts the attribute name as the first parameter (the second parameter is the default value) and returns its value.

    <div class="{{ $attributes->get("class", "default") }}">
        <!-- Component content -->
    </div>

Detecting attributes

You can use the has() method to get a specific attribute value. This method accepts the attribute name as a parameter and will return a boolean value.

    @if($attributes->has("class"))
        <div class="{{ $attributes->get("class") }}">
            <!-- Component content -->
        </div>
    @endif

Merging attributes

At some point, you may need to specify the default value of an attribute, or incorporate other values into certain attributes of the component. For this, you can use the merge method of the property bag:

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

Suppose we use this component as shown below:

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

The final rendered component HTML will look like this:

    <div class="alert alert-error mb-4">
        <!-- The content of the $message variable -->
    </div>

By default, only the class attributes will be merged, and other attributes will be directly overwritten. The following situations will occur:

// definition
<div {{ $attributes->merge(['class' =>'alert alert-'.$type,'other-attr' =>'foo']) }}>{{ $message }}</div>
// use
<x-alert type="error" :message="$message" class="mb-4" other-attr="bar"/>
// present
<div class="alert alert-error mb-4" other-attr="bar"><!-- The content of the $message variable --></div>

As in the above case, if you need to merge the other-attr attributes, you can use the following method to add the second parameter true to the merge() method:

// definition
<div {{ $attributes->merge(['class' =>'alert alert-'.$type,'other-attr' =>'foo'], true) }}>{{ $message }}</ div>
// use
<x-alert type="error" :message="$message" class="mb-4" other-attr="bar"/>
// present
<div class="alert alert-error mb-4" other-attr="foo bar"><!-- The content of the $message variable --></div>

Slot

Usually, you need to pass additional content to the component through slots. Assume that the alert component we created has the following markup:

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

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

We can pass content to slots by injecting content into the component:

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

Sometimes a component may need to place multiple different slots in different positions within it. Let's modify the alert component to allow the injection of title.

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

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

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

You can use the x-slot tag to define the content of a named slot. Other content not in the x-slot tag will be passed to the components in the $slot variable:

    <x-alert>
        <x-slot name="title">
            Server Error
        </x-slot>

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

Inline components

For small components, managing component classes and component view templates can be troublesome. Therefore, you can return the content of the component from the render method:

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

Anonymous components

Like inline components, anonymous components provide a mechanism for managing components through a single file. However, the anonymous component uses a single view file with no associated classes. To define an anonymous component, you only need to place the Blade template in the /storage/view/components directory. For example, suppose you define a component in /storage/view/components/alert.blade.php:

    <x-alert/>

If the component is in a subdirectory of the components directory, you can use the . character to specify its path. For example, if the component is defined in /storage/view/components/inputs/button.blade.php, you can render it like this:

    <x-inputs.button/>

Anonymous component data and attributes

Since anonymous components do not have any associated classes, you may want to distinguish which data should be passed to the component as variables and which properties should be stored in property package.

You can use the @props directive at the top level of the Blade template of the component to specify which properties should be used as data variables. All other properties in the component will be provided in the form of a property bag. If you want to specify a default value for a certain data variable, you can use the attribute name as the array key and the default value as the array value:

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

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

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

Dynamic components

Sometimes, you may need to render a component, but do not know which one to render before running. In this case, you can use the built-in dynamic-component component to render a component based on values or variables:

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

Automatic component loading

By default, components under App\View\Component\ and components. are automatically registered. You can also modify this configuration through the configuration file:

config/autoload/view.php

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

View space

By defining the view space, you can easily use the view file in your extension package. You only need to add a line of configuration in ConfigProvider:

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

After installing the extension package, you can override the view in the extension package by defining a view file with the same path in the project's /storage/view/vendor/package-name.

Optional middleware

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

Automatically share the errors in the session to the view, relying on the hyperf/session component

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

Automatically catch exceptions in validation and add them to session, relying on hyperf/session and hyperf/validation components

Other commands

Automatic installation of view-engine, translation and validation component related configuration

php bin/hyperf.php view:publish