Laravel. Шаблонизатор Blade. Часть 2 из 2

06.11.2020

Теги: LaravelPHPWeb-разработкаКомпонентТеорияФреймворкШаблонизаторШаблонСайта

Использование стеков

Стек — это заглушка где-то в родительском шаблоне. А дочерние шаблоны могут вставлять свое содержимое на место этой заглушки. Причем старое содержимое при этом не теряется, а новое содержимое вставляется перед старым или после него. Это очень похоже на php-функции array_push() и array_unshift(), которые добавляют элемент в конец или начало массива.

Создадим родительский шаблон (общий шаблон сайта):

<!-- resources/views/layouts/site.blade.php -->
<html>
    <head>
        <title>@yield('title', 'Заголовок по умолчанию')</title>
        <link rel="stylesheet" href="/css/common.css">
        <!-- заглушка, куда будет помещен контент из стека -->
        @stack('styles')
    </head>
    <body>
        <div id="content">
            @yield('content')
        </div>
    </body>
</html>

Создадим дочерний шаблон (каталог товаров):

<!-- resources/views/catalog/index.blade.php -->
@extends('layouts.site')

@section('title', 'Каталог товаров')

@push('styles')
    <!-- добавляем что-нибудь в конец стека -->
    <link rel="stylesheet" href="/css/append.css">
@endpush

@section('content')
    <p>Это контент каталога товаров.</p>
@endsection

Создадим дочерний-дочерний шаблон (товар каталога):

<!-- resources/views/catalog/product.blade.php -->
@extends('catalog.index')

@section('title', 'Товар каталога')

@prepend('styles')
    <!-- добавляем что-нибудь в начало стека -->
    <link rel="stylesheet" href="/css/prepend.css">
@endprepend

@section('content')
    <p>Это контент товара каталога.</p>
@endsection

В результате страница товара каталога будет иметь вид:

<html>
    <head>
        <title>Товар каталога</title>
        <link rel="stylesheet" href="/css/common.css">
        <!-- заглушка, куда будет помещен контент из стека -->
        <link rel="stylesheet" href="/css/prepend.css">
        <link rel="stylesheet" href="/css/append.css">
    </head>
    <body>
        <div id="content">
            <p>Это контент товара каталога.</p>
        </div>
    </body>
</html>

Компоненты и слоты

Допустим, нам надо показывать пользователю сообщение об ошибке. Обертку для сообщения мы выносим в отдельный шаблон:

<!-- resources/views/parts/alert.blade.php -->
<div class="alert alert-danger alert-dismissible" role="alert">
    <button type="button" class="close" data-dismiss="alert" aria-label="Закрыть">
        <span aria-hidden="true">&times;</span>
    </button>
    {{ $message }}
</div>

Теперь можем где-то в другом шаблоне показать сообщение об ошибке:

<!-- какой-то другой шаблон, где надо показать сообщение об ошибке -->
@include('parts.alert', ['message' => '<p>Lorem ipsum ... voluptatibus.</p><p>Blanditiis consequatur ... repellendus.</p>'])

Сообщение об ошибке может быть большим, оно может быть отформатировано с помощью тегов <p>, <ul>, <strong>. И все это приходится передавать в шаблон parts.alert одной строкой. Давайте посмотрим, как можно сделать то же самое с помощью компонента и слота:

<!-- resources/views/parts/alert.blade.php -->
<div class="alert alert-danger alert-dismissible" role="alert">
    <button type="button" class="close" data-dismiss="alert" aria-label="Закрыть">
        <span aria-hidden="true">&times;</span>
    </button>
    {{ $slot }}
</div>

Теперь можем где-то в другом шаблоне показать сообщение об ошибке:

<!-- какой-то другой шаблон, где надо показать сообщение об ошибке -->
@component('parts.alert')
    <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis consequatur cumque
    delectus dolorem itaque obcaecati provident, qui quibusdam repellendus saepe.
    <p>
    <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci aut dolore ea illum
    impedit molestias nostrum omnis quasi repellat veritatis.
    </p>
@endcomponent

Все, что находится между @component и @endcomponent, передается в шаблон parts.alert и доступно в переменной $slot.

Составные слоты

Можно передать в шаблон parts.alert не одну переменную, а две — $heading и $slot:

<!-- resources/views/parts/alert.blade.php -->
<div class="alert alert-danger alert-dismissible" role="alert">
    <button type="button" class="close" data-dismiss="alert" aria-label="Закрыть">
        <span aria-hidden="true">&times;</span>
    </button>
    <h4 class="alert-heading">{{ $heading }}</h4>
    {{ $slot }}
</div>
<!-- какой-то другой шаблон, где надо показать сообщение об ошибке -->
@component('parts.alert')
    @slot('heading')
        Alert! Something wrong!
    @endslot
    <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis consequatur cumque
    delectus dolorem itaque obcaecati provident, qui quibusdam repellendus saepe.
    <p>
    <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci aut dolore ea illum
    impedit molestias nostrum omnis quasi repellat veritatis.
    </p>
@endcomponent

Или даже три — $heading, $slot и $class:

<!-- resources/views/parts/alert.blade.php -->
<div class="alert alert-{{ $class }} alert-dismissible" role="alert">
    <button type="button" class="close" data-dismiss="alert" aria-label="Закрыть">
        <span aria-hidden="true">&times;</span>
    </button>
    <h4 class="alert-heading">{{ $heading }}</h4>
    {{ $slot }}
</div>
<!-- какой-то другой шаблон, где надо показать сообщение об ошибке -->
@component('parts.alert', ['class' => 'danger'])
    @slot('heading')
        Alert! Something wrong!
    @endslot
    <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis consequatur cumque
    delectus dolorem itaque obcaecati provident, qui quibusdam repellendus saepe.
    <p>
    <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci aut dolore ea illum
    impedit molestias nostrum omnis quasi repellat veritatis.
    </p>
@endcomponent

Создание своих директив

Какова цель создания собственных директив? Во-первых — чтобы не загружать шаблон логическими конструкциями, во-вторых — чтобы избежать дублирования кода. Например, в личном кабинете у администратора сайта должны быть кнопки редактирования и удаления, а у обычного пользователя сайта таких кнопок быть не должно. Сперва надо проверить, что пользователь аутентифицирован, а потом — что он является администратором.

Директивы создаются в методе boot() класса AppServiceProvider и определяются следующим образом:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

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

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot() {
        Blade::directive('some_name', function($expression) {
            // какой-то код
            return $something;
        });
    }
}

1. Директива «Иконка»

Если на сайте используются иконки Font Awesome, то в шаблонах много кода типа:

<i class="fas fa-shopping-basket"></i> <!-- корзина покупателя -->
<i class="fas fa-arrow-circle-right"></i> <!-- стрелка вправо -->
<i class="fas fa-download"></i> <!-- скачать файл -->

Давайте создадим директиву @icon(), чтобы вставлять иконки по имени:

class AppServiceProvider extends ServiceProvider {
    public function boot() {
        Blade::directive('icon', function($expression) {
            $name = str_replace("'", '', $expression);
            return '<i class="fas fa-' . $name . '"></i>';
        });
    }
}

Теперь можем вставлять иконки с использованием новой директивы:

<p>Цена: {{ number_format($product->price, 2, '.', '') }}</p>
<!-- Форма для добавления товара в корзину -->
<form action="{{ route('basket.add', ['id' => $product->id]) }}" method="post">
    @csrf
    <button type="submit" class="btn btn-success">
        @icon('shopping-basket') Добавить в корзину
    </button>
</form>

2. Директива «Цена»

В примере выше используется функция number_format() для форматирования цены. Давайте создадим директиву @price():

class AppServiceProvider extends ServiceProvider {
    public function boot() {
        Blade::directive('icon', function($expression) {
            $name = str_replace("'", '', $expression);
            return '<i class="fas fa-' . $name . '"></i>';
        });
        Blade::directive('price', function($expression) {
            return "<?php echo number_format($expression, 2, '.', ''); ?>";
        });
    }
}

Теперь можно форматировать цену с использованием новой директивы:

<p>Цена: @price($product->price)</p>
<!-- Форма для добавления товара в корзину -->
<form action="{{ route('basket.add', ['id' => $product->id]) }}" method="post">
    @csrf
    <button type="submit" class="btn btn-success">
        @icon('shopping-basket') Добавить в корзину
    </button>
</form>

3. Директива «Админ»

Директива @admin, которая проверяет, что пользователь аутентифицирован и является администратором:

class AppServiceProvider extends ServiceProvider {
    public function boot() {
        Blade::directive('icon', function($expression) {
            $name = str_replace("'", '', $expression);
            return '<i class="fas fa-' . $name . '"></i>';
        });
        Blade::directive('price', function($expression) {
            return "<?php echo number_format($expression, 2, '.', ''); ?>";
        });
        Blade::if('admin', function() {
            return auth()->check() && auth()->user()->admin;
        });
    }
}
@admin
    <p>Это администратор сайта</p>
@else
    <p>Это обычный пользователь</p>
@endadmin

Внедрение сервисов

Директива @inject() служит для извлечения сервиса из сервис-контейнера. Первый аргумент — это имя переменной, в которую будет помещён сервис. А второй — имя класса или интерфейса сервиса, который нужно извлечь.

@inject('metrics', 'App\Services\MetricsService')
<p>Месячный доход: {{ $metrics->monthlyRevenue() }}</p>

Поиск: Laravel • PHP • Web-разработка • Теория • Фреймворк • Шаблонизатор • Шаблон сайта • Blade • Стек • Слот • Компонент

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.