Шаблоны Blade

Введение

Blade — простой, но мощный шаблонизатор, поставляемый с Laravel. В отличие от других популярных шаблонизаторов для PHP Blade не ограничивает вас в использовании чистого PHP-кода в ваших шаблонах. На самом деле все шаблоны Blade скомпилированы в чистый PHP-код и кешированы, пока в них нет изменений, а значит, Blade практически не нагружает ваше приложение. Файлы шаблонов Blade используют расширение .blade.php и обычно хранятся в директории resources/views.

Наследование шаблонов

Определение макета

Два основных преимущества использования Blade — наследование шаблонов и секции. Для начала давайте рассмотрим простой пример. Во-первых, изучим макет "главной" страницы. Поскольку многие веб-приложения используют один общий макет для разных страниц, удобно определить этот макет как один шаблон Blade:

<!-- Хранится в 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 для указания макета, который должен быть "унаследован" дочерним шаблоном. Шаблоны, которые наследуют макет Blade, могут внедрять содержимое в секции макета с помощью директив @section. Запомните, как видно из приведённого выше примера, содержимое этих секций будет отображено в макете при помощи @yield:

<!-- Хранится в resources/views/child.blade.php -->

@extends('layouts.app')

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

@section('sidebar')
    @

    <p>Это дополнение к основной боковой панели.</p>
@endsection

@section('content')
    <p>Это содержимое тела страницы.</p>
@endsection

В этом примере секция sidebar использует директиву @ для дополнения (а не перезаписи) содержимого к боковой панели макета. Директива @ будет заменена содержимым макета при отрисовке шаблона.

Blade-шаблоны могут быть возвращены из роутов при помощи глобального хелпера view:

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>Ой!</strong> Что-то пошло не так!
@endcomponent

Иногда бывает полезно определить несколько слотов для компонента. Давайте модифицируем наш компонент оповещений, чтобы разрешить внедрение "заголовка". Именованные слоты могут отображаться просто путем "отражения" переменной, которая соответствует их имени:

<!-- /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 }}.

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

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

{note} Blade-оператор {{ }} автоматически отправляется через PHP-функцию htmlspecialchars для предотвращения XSS-атак.

Вывод неэкранированных данных

По умолчанию Blade-оператор {{ }} автоматически отправляется через PHP-функцию htmlspecialchars для предотвращения XSS-атак. Если вы не хотите экранировать данные, используйте такой синтаксис:

Hello, {!! $name !!}.

{note} Будьте очень осторожны и экранируйте переменные, которые содержат ввод от пользователя. Всегда используйте экранирование синтаксисом с двойными скобками, чтобы предотвратить XSS-атаки при отображении предоставленных пользователем данных.

Фреймворки Blade и JavaScript

Поскольку многие JavaScript-фреймворки тоже используют фигурные скобки для обозначения того, что данное выражение должно быть отображено в браузере, то вы можете использовать символ @, чтобы указать механизму отрисовки Blade, что выражение должно остаться нетронутым. Например:

<h1>Laravel</h1>

Hello, @{{ name }}.

В этом примере Blade удалит символ @, но выражение {{ name }} останется нетронутым, что позволит вашему JavaScript-фреймворку отрисовать его вместо Blade.

Директива @verbatim

Если вы выводите JavaScript-переменные в большой части вашего шаблона, вы можете обернуть HTML директивой @verbatim , тогда вам не нужно будет ставить символ @ перед каждым оператором вывода Blade:

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

Управляющие конструкции

В дополнение к наследованию шаблонов и отображению данных Blade предоставляет удобные сокращения для распространенных управляющих конструкций PHP, таких как условные операторы и циклы. Эти сокращения обеспечивают очень чистый и краткий способ работы с управляющими конструкциями PHP и при этом остаются очень похожими на свои PHP-прообразы.

Оператор If

Вы можете конструировать оператор if при помощи директив @if, @elseif, @else и @endif. Эти директивы работают идентично своим 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

Циклы

В дополнение к условным операторам Blade предоставляет простые директивы для работы с конструкциями циклов PHP. Данные директивы тоже идентичны их PHP-прообразам:

@for ($i = 0; $i < 10; $i++)
    Текущее значение: {{ $i }}
@endfor

@foreach ($users as $user)
    <p>Это пользователь {{ $user->id }}</p>
@endforeach

@forelse ($users as $user)
    <li>{{ $user->name }}</li>
@empty
    <p>Нет пользователей</p>
@endforelse

@while (true)
    <p>Этот цикл будет длиться вечно.</p>
@endwhile

{tip} При работе с циклами вы можете использовать переменную loop для получения полезной информации о цикле, например, находитесь ли вы на первой или последней итерации цикла.

При работе с циклами вы также можете закончить цикл или пропустить текущую итерацию:

@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)
        Это первая итерация.
    @endif

    @if ($loop->last)
        Это последняя итерация.
    @endif

    <p>Это пользователь {{ $user->id }}</p>
@endforeach

Если вы во вложенном цикле, вы можете обратиться к переменной $loop родительского цикла через свойство parent:

@foreach ($users as $user)
    @foreach ($user->posts as $post)
        @if ($loop->parent->first)
            Это первая итерация родительского цикла.
        @endif
    @endforeach
@endforeach

Переменная $loop одержит также множество других полезных свойств:

Свойство Описание
$loop->index Индекс текущей итерации цикла (начинается с 0).
$loop->iteration Текущая итерация цикла (начинается с 1).
$loop->remaining Число оставшихся итераций цикла.
$loop->count Общее число элементов итерируемого массива.
$loop->first Первая ли это итерация цикла.
$loop->last Последняя ли это итерация цикла.
$loop->depth Уровень вложенности текущего цикла.
$loop->parent Переменная loop родительского цикла, для вложенного цикла.

Комментарии

Blade также позволяет вам определить комментарии в ваших шаблонах. Но в отличие от HTML-комментариев, Blade-комментарии не включаются в HTML-код, возвращаемый вашим приложением:

{{-- Этого комментария не будет в итоговом HTML --}}

PHP

В некоторых случаях бывает полезно встроить PHP-код в ваши шаблоны. Вы можете использовать Blade-директиву @php для выполнения блока чистого PHP в вашем шаблоне:

@php
    //
@endphp

{tip} Несмотря на то, что в Blade есть эта возможность, её частое использование может быть сигналом того, что у вас слишком много встроенной в шаблон логики.

Включение подшаблонов

{tip} Включение подшаблонов является повторение функционала компонентов, только в "старом" стиле.

Blade-директива @include позволяет вам включать Blade-шаблон в другой шаблон. Все переменные, доступные родительскому шаблону, будут доступны и включаемому шаблону:

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

    <form>
        <!-- Содержимое формы -->
    </form>
</div>

Хотя включаемый шаблон унаследует все данные, доступные родительскому шаблону, вы также можете передать в него массив дополнительных данных:

@include('view.name', ['some' => 'data'])

Само собой, если вы попробуете сделать @include шаблона, которого не существует, то Laravel выдаст ошибку. Если вы хотите включить шаблон, которого может не существовать, вам надо использовать директиву @includeIf:

@includeIf('view.name', ['some' => 'data'])

Если вы хотите включить (@include) шаблон в зависимости от логического условия, можно использовать директиву @includeWhen:

@includeWhen($boolean, 'view.name', ['some' => 'data'])

{note} Вам следует избегать использования констант __DIR__ и __FILE__ в ваших Blade-шаблонах, поскольку они будут ссылаться на расположение кешированных, скомпилированных шаблонов.

Отрисовка шаблонов для коллекций

Вы можете комбинировать циклы и включения в одной строке при помощи Blade-директивы @each:

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

Первый аргумент — часть шаблона, которую надо отрисовать для каждого элемента массива или коллекции. Второй аргумент — массив или коллекция для перебора, а третий — имя переменной, которое будет назначено для текущей итерации в шаблоне. Например, если вы перебираете массив jobs, то скорее всего захотите обращаться к каждому элементу как к переменной job внутри вашей части шаблона. Ключ для текущей итерации будет доступен в виде переменной key в вашей части шаблона.

Вы также можете передать четвёртый аргумент в директиву @each. Этот аргумент определяет шаблон, который будет отрисовано, если данный массив пуст.

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

Стеки

Blade позволяет использовать именованные стеки, которые могут быть отрисованы где-нибудь ещё в другом шаблоне или макете. Это удобно в основном для указания любых JavaScript-библиотек, требуемых для ваших дочерних шаблонов:

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

"Пушить" в стек можно сколько угодно раз. Для отрисовки всего содержимого стека передайте имя стека в директиву @stack:

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

    @stack('scripts')
</head>

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

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

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

<div>
    Месячный доход: {{ $metrics->monthlyRevenue() }}.
</div>

Наследование 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
{
    /**
     * Выполнение послерегистрационной загрузки сервисов.
     *
     * @return void
     */
    public function boot()
    {
        Blade::directive('datetime', function ($expression) {
            return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
        });
    }

    /**
     * Регистрация привязок в контейнере.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Как видите, мы прицепили метод format к тому выражению, которое передаётся в директиву. Поэтому финальный PHP-код, сгенерированный этой директивой, будет таким:

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

{note} После изменения логики директивы Blade вам надо удалить все кешированные шаблоны Blade. Это можно сделать Artisan-командой view:clear.