Шаблоны в Laravel

Бизюк Андрей

ВГТУ

2024-12-03

Шаблонизатор Blade

Введение

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

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

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

Отображение данных

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

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

Вы можете отобразить содержимое переменной name следующим образом:

Hello, {{ $name }}.

Уведомление

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

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

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

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

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

Hello, {!! $name !!}.

Директивы Blade

Описание

Помимо наследования шаблонов и отображения данных, 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 определена и не равна `null` ...
@endisset

@empty($records)
    // Переменная $records считается «пустой» ...
@endempty

Директивы аутентификации

Директивы @auth и @guest могут использоваться для быстрого определения, является ли текущий пользователь аутентифицированным или считается гостем:

@auth
    // Пользователь аутентифицирован ...
@endauth

@guest
    // Пользователь не аутентифицирован ...
@endguest

При необходимости вы можете указать охранника аутентификации для проверки при использовании директив @auth и @guest:

@auth('admin')
    // Пользователь аутентифицирован ...
@endauth

@guest('admin')
    // Пользователь не аутентифицирован ...
@endguest

Директивы окружения

Вы можете проверить, запущено ли приложение в эксплуатационном окружении, с помощью директивы @production:

@production
    // Содержимое, отображаемое только в эксплуатационном окружении ...
@endproduction

Или вы можете определить, работает ли приложение в конкретной среде, с помощью директивы @env:

@env('staging')
    // Приложение запущено в «переходном» окружении ...
@endenv

@env(['staging', 'production'])
    // Приложение запущено в «переходном» или «рабочем» окружении ...
@endenv

Директивы секций

Вы можете определить, есть ли в секции наследуемого шаблона содержимое, используя директиву @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

Директивы сессии

Директива @session может использоваться для определения существования значения сессии. Если значение сессии существует, содержимое шаблона внутри директив @session и @endsession будет оценено. Внутри содержимого директивы @session вы можете использовать переменную $value для вывода значения сессии:

@session('status')
    <div class="p-4 bg-green-100">
        {{ $value }}
    </div>
@endsession

Операторы 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. Опять же, каждая из этих директив работает так же, как и их аналоги в 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

При использовании циклов вы также можете пропустить текущую итерацию или завершить цикл, используя директивы @continue и @break:

@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

Во время повторения цикла foreach доступна переменная $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 the 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 Переменная родительского цикла во вложенном цикле.

Css-классы и стили по условию

Директива @class осуществляет построение строки css-классов исходя из заданных условий. Директива принимает массив классов, где ключ массива содержит класс или классы, которые вы хотите добавить, а значение является булевым выражением. Если элемент массива имеет числовой ключ, он всегда будет включен в отрисованный список классов:

@php
    $isActive = false;
    $hasError = true;
@endphp

<span @class([
    'p-4',
    'font-bold' => $isActive,
    'text-gray-500' => ! $isActive,
    'bg-red' => $hasError,
])></span>

<span class="p-4 text-gray-500 bg-red"></span>

Также, директива @style может использоваться, чтобы условно добавлять встроенные стили CSS к HTML-элементу:

@php
    $isActive = true;
@endphp
<span @style([
    'background-color: red',
    'font-weight: bold' => $isActive,
])></span>
<span style="background-color: red; font-weight: bold;"></span>

Дополнительные атрибуты

Для удобства, вы можете использовать директиву @checked, чтобы легко указать, должен ли данный флажок HTML быть “отмеченным”. Эта директива будет выводить checked, если условие выполнится:

<input type="checkbox"
        name="active"
        value="active"
        @checked(old('active', $user->active)) />

Аналогично, директива @selected может использоваться, чтобы указать, должен ли заданный вариант выбора быть “выбранным”:

<select name="version">
    @foreach ($product->versions as $version)
        <option value="{{ $version }}" @selected(old('version') == $version)>
            {{ $version }}
        </option>
    @endforeach
</select>

Кроме того, директива @disabled может использоваться, чтобы указать, должен ли данный элемент быть “отключенным”:

<button type="submit" @disabled($errors->isNotEmpty())>Отправить</button>

Более того, директива @readonly может быть использована, чтобы указать, должен ли данный элемент быть “только для чтения”:

<input type="email"
        name="email"
        value="[email protected]"
        @readonly($user->isNotAdmin()) />

Кроме того, директива @required может быть использована, чтобы указать, должен ли данный элемент быть “обязательным”:

<input type="text" name="name" @required(true) />
<input type="text"
        name="title"
        value="title"
        @required($user->isAdmin()) />

Подключение дочерних шаблонов

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

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

    <form>
        <!-- Form Contents -->
    </form>
</div>

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

@include('view.name', ['status' => 'complete'])

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

@includeIf('view.name', ['status' => 'complete'])

Если вы хотите включить шаблон в зависимости от результата логического выражения, возвращающего либо true, либо false, то используйте директивы @includeWhen и @includeUnless:

@includeWhen($boolean, 'view.name', ['status' => 'complete'])

@includeUnless($boolean, 'view.name', ['status' => 'complete'])

Чтобы включить первый существующий шаблон из переданного массива шаблонов, вы можете использовать директиву includeFirst:

@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])

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

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

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

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

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

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

Директива @once

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

@once
    @push('scripts')
        <script>
            // Ваш JavaScript...
        </script>
    @endpush
@endonce

Поскольку директива @once часто используется в сочетании с директивами @push или @prepend, для удобства доступны директивы @pushOnce и @prependOnce:

@pushOnce('scripts')
    <script>
        // Ваш JavaScript...
    </script>
@endPushOnce

Необработанный PHP

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

@php
    $counter = 1;
@endphp

Или, если вам нужно использовать только PHP для импорта класса, вы можете использовать директиву @use:

@use('App\Models\Flight')

Второй аргумент может быть использован в директиве @use для указания псевдонима импортируемого класса:

@use('App\Models\Flight', 'FlightModel')

Комментарии

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

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

Компоненты

Описание

Компоненты и слоты предоставляют те же преимущества, что и секции, макеты и включение шаблона из другого шаблона; однако, некоторым может быть легче понять мысленную модель компонентов и слотов. Есть два подхода к написанию компонентов: компоненты на основе классов и анонимные компоненты.

Чтобы создать компонент на основе класса, вы можете использовать команду make:component Artisan. Чтобы проиллюстрировать, как использовать компоненты, мы создадим простой компонент Alert. Команда make:component поместит компонент в каталог app/View/Components:

php artisan make:component Alert

Команда make: component также создаст шаблон для компонента. Шаблон будет помещен в каталог resources/views/components. При написании компонентов для вашего собственного приложения компоненты автоматически обнаруживаются в каталогах app/View/Components и resources/views/components, поэтому дополнительная регистрация компонентов обычно не требуется.

Вы также можете создавать компоненты в подкаталогах:

php artisan make:component Forms/Input

Приведенная выше команда создаст компонент Input в каталоге app/View/Components/Forms, а шаблон будет помещен в каталог resources/views/components/forms.

Если вы хотите создать анонимный компонент (компонент только с шаблоном в Blade без класса), вы можете использовать флаг --view при вызове команды make:component:

php artisan make:component forms.input --view

Вышеприведенная команда создаст файл Blade по пути resources/views/components/forms/input.blade.php, который может быть отображен как компонент с помощью <x-forms.input />.

Отрисовка компонентов

Для отображения компонента вы можете использовать тег компонента Blade в одном из ваших шаблонов Blade. Теги компонентов Blade начинаются со строки x-, за которой следует имя в «шашлычном регистре» класса компонента:

<x-alert/>

<x-user-profile/>

Если класс компонента имеет вложенность в каталоге app/View/Components, то вы можете использовать символ . для обозначения вложенности каталогов. Например, если мы предполагаем, что компонент находится в app/View/Components/Inputs/Button.php, то мы можем отобразить его так:

<x-inputs.button/>

Если вы хотите выборочно отображать ваш компонент, вы можете указать метод shouldRender в классе вашего компонента. Если результат метода shouldRender равен false, то компонент не будет отображаться:

use Illuminate\Support\Str;

/**
 * Определяет, должен ли компонент отображаться
 */
public function shouldRender(): bool
{
    return Str::length($this->message) > 0;
}

Передача данных компонентам

Вы можете передавать данные в компоненты Blade, используя атрибуты HTML. Жестко запрограммированные примитивные значения могут быть переданы компоненту с помощью простых строк атрибутов HTML. Выражения и переменные PHP следует передавать компоненту через атрибуты, которые используют символ : в качестве префикса:

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

Вы должны определить необходимые данные компонента в его конструкторе класса. Все общедоступные свойства компонента будут автоматически доступны в шаблоне компонента. Нет необходимости передавать данные в шаблон из метода render компонента:

<?php

namespace App\View\Components;

use Illuminate\View\Component;
use Illuminate\View\View;

class Alert extends Component
{
   public function __construct(
        public string $type,
        public string $message,
    ) {}

    /**
     * Получить шаблон / содержимое, представляющее компонент.
     */
    public function render(): View
    {
        return view('components.alert');
    }
}

Когда ваш компонент визуализируется, вы можете отображать содержимое общедоступных переменных вашего компонента, выводя переменные по имени:

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

Именование

Аргументы конструктора компонентов следует указывать с помощью camelCase, а при обращении к именам аргументов в ваших атрибутах HTML следует использовать kebab-case. Например, учитывая следующий конструктор компонента:

/**
 * Создать экземпляр компонента.
 */
public function __construct(
    public string $alertType,
) {}

Аргумент $alertType может быть передан компоненту следующим образом:

<x-alert alert-type="danger" />

Сокращенный синтаксис атрибутов

При передаче атрибутов компонентам вы также можете использовать “сокращенный синтаксис атрибутов”. Это удобно, поскольку имена атрибутов часто совпадают с именами переменных, к которым они относятся:

{{-- Сокращенный синтаксис атрибутов... --}}
<x-profile :$userId :$name />
{{-- Эквивалентно... --}}
<x-profile :user-id="$userId" :name="$name" />

Методы компонента

В дополнение к общедоступным переменным, доступным для вашего шаблона компонента, могут быть вызваны любые общедоступные методы компонента. Например, представьте компонент, у которого есть метод isSelected:

/**
 * Определить, является ли переданная опция выбранной.
 */
public function isSelected(string $option): bool
{
    return $option === $this->selected;
}

Вы можете выполнить этот метод из своего шаблона компонента, вызвав переменную, соответствующую имени метода:

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

Атрибуты компонента

Мы уже рассмотрели, как передавать атрибуты данных в компонент; иногда требуется указать дополнительные атрибуты HTML, такие, как class, которые не являются частью данных, необходимых для функционирования компонента. Как правило, вы хотите передать эти дополнительные атрибуты корневому элементу шаблона компонента. Например, представьте, что мы хотим отобразить компонент alert следующим образом:

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

Все атрибуты, которые не являются частью конструктора компонента, будут автоматически добавлены в «коллекцию атрибутов» компонента. Эта коллекция атрибутов автоматически становится доступной для компонента через переменную $attributes. Все атрибуты могут отображаться в компоненте путем вывода этой переменной:

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

Слоты

Описание

Вам часто потребуется передавать дополнительный контент вашему компоненту через «слоты». Слоты компонентов отображаются путем вывода переменной $slot. Чтобы изучить эту концепцию, представим, что компонент alert имеет следующую разметку:

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

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

Мы можем передавать контент в slot, вставив контент в компонент:

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

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

<!-- /resources/views/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:title>
        Server Error
    </x-slot>

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

Вы можете вызвать метод слота isEmpty, чтобы определить, содержит ли он контент:

<span class="alert-title">{{ $title }}</span>
<div class="alert alert-danger">
    @if ($slot->isEmpty())
        This is default content if the slot is empty.
    @else
        {{ $slot }}
    @endif
</div>

Кроме того, метод hasActualContent может быть использован для определения, содержит ли слот какой-либо «фактический» контент, не являющийся HTML-комментарием:

@if ($slot->hasActualContent())
    The scope has non-comment content.
@endif

Атрибуты слотов

Как и в компонентах Blade, вы можете назначать слотам дополнительные атрибуты, например, имена классов CSS:

<x-card class="shadow-sm">
    <x-slot:heading class="font-bold">
        Heading
    </x-slot>

    Content

    <x-slot:footer class="text-sm">
        Footer
    </x-slot>
</x-card>

Чтобы взаимодействовать с атрибутами слота, можно обратиться к свойству attributes переменной слота.

@props([
    'heading',
    'footer',
])

<div {{ $attributes->class(['border']) }}>
    <h1 {{ $heading->attributes->class(['text-lg']) }}>
        {{ $heading }}
    </h1>

    {{ $slot }}

    <footer {{ $footer->attributes->class(['text-gray-700']) }}>
        {{ $footer }}
    </footer>
</div>

Создание макетов

Макеты с использованием компонентов

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

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

Например, представьте, что мы создаем приложение со списком задач. Мы могли бы определить компонент layout, который выглядит следующим образом:

<!-- resources/views/components/layout.blade.php -->

<html>
<head>
    <title>{{ $title ?? 'Todo Manager' }}</title>
</head>
<body>
    <h1>Todos</h1>
    <hr/>
    {{ $slot }}
</body>
</html>

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

Как только компонент layout определен, мы можем создать шаблон Blade, который будет использовать этот компонент. В этом примере мы определим простой шаблон, который отображает наш список задач:

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

<x-layout>
@foreach ($tasks as $task)
    {{ $task }}
@endforeach
</x-layout>

Помните, что содержимое, внедренное в компонент, по умолчанию будет передано переменной $slot компонента layout. Как вы могли заметить, наш layout также учитывает слот $title, если он предусмотрен; в противном случае отображается заголовок по умолчанию. Мы можем добавить другой заголовок из нашего шаблона списка задач, используя стандартный синтаксис слотов.

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

<x-layout>
<x-slot name="title">
    Custom Title
</x-slot>

@foreach ($tasks as $task)
    {{ $task }}
@endforeach
</x-layout>

Теперь, когда мы определили наш макет и шаблоны списка задач, нам просто нужно вернуть представление task из маршрута:

use App\Models\Task;

Route::get('/tasks’, function () { return view('tasks’, ['tasks’ => Task::all()]); });