MidТеория7 min

Основы Blade

Синтаксис Blade: вывод данных, условные директивы, циклы, @class, @checked/@selected/@disabled и другие директивы шаблонизатора

Основы шаблонизатора Blade

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

Вывод данных

Экранированный вывод {{ }}

Двойные фигурные скобки автоматически применяют htmlspecialchars() для защиты от XSS-атак:

{{-- Safe output - HTML entities will be escaped --}}
<h1>{{ $user->name }}</h1>
<p>{{ $article->title }}</p>

{{-- Expression evaluation --}}
<span>{{ $user->created_at->diffForHumans() }}</span>
<span>{{ count($items) }} items</span>
<span>{{ $price * 1.2 }}</span>

{{-- Ternary operator --}}
<span>{{ $user->nickname ?? $user->name }}</span>
<span>{{ isset($title) ? $title : 'Default Title' }}</span>

Неэкранированный вывод {!! !!}

Для вывода сырого HTML без экранирования:

{{-- Raw HTML output - NOT escaped, use with caution! --}}
{!! $article->body !!}
{!! $user->bio_html !!}

{{-- Markdown rendered to HTML --}}
{!! Str::markdown($post->content) !!}

::alert{type="danger"} Осторожно! {!! !!} не экранирует данные. Никогда не используйте его для пользовательского ввода без предварительной санитизации. Это прямой путь к XSS-атакам. ::

Вывод JSON

{{-- Render data as JSON for JavaScript --}}
<script>
    var app = {{ Js::from($arrayData) }};
</script>

{{-- Equivalent to: --}}
<script>
    var app = JSON.parse('{!! json_encode($arrayData) !!}');
</script>

Blade и JavaScript-фреймворки

Если нужно оставить {{ }} для JavaScript (Vue.js, Alpine.js), используйте @:

{{-- Tells Blade to leave this as-is --}}
<span>@{{ vueVariable }}</span>

{{-- Or use @verbatim for larger blocks --}}
@verbatim
    <div id="app">
        <span>{{ message }}</span>
        <span>{{ count }}</span>
    </div>
@endverbatim

Условные директивы

@if / @elseif / @else / @endif

@if (count($records) === 1)
    <p>There is one record.</p>
@elseif (count($records) > 1)
    <p>There are {{ count($records) }} records.</p>
@else
    <p>No records found.</p>
@endif

@unless / @endunless

@unless - это инвертированный @if (эквивалент @if (!condition)):

@unless (auth()->check())
    <a href="{{ route('login') }}">Please log in</a>
@endunless

{{-- Equivalent to --}}
@if (!auth()->check())
    <a href="{{ route('login') }}">Please log in</a>
@endif

@isset / @endisset

Проверяет, определена ли переменная и не является ли она null:

@isset($user->avatar)
    <img src="{{ $user->avatar }}" alt="{{ $user->name }}">
@endisset

{{-- Equivalent to --}}
@if (isset($user->avatar))
    <img src="{{ $user->avatar }}" alt="{{ $user->name }}">
@endif

@empty / @endempty

Проверяет, является ли выражение "пустым" (empty в PHP):

@empty($records)
    <div class="alert alert-info">
        <p>No records to display.</p>
    </div>
@endempty

@auth / @guest

Проверка аутентификации пользователя:

@auth
    <p>Welcome, {{ auth()->user()->name }}!</p>
    <form method="POST" action="{{ route('logout') }}">
        @csrf
        <button type="submit">Logout</button>
    </form>
@endauth

@guest
    <a href="{{ route('login') }}">Login</a>
    <a href="{{ route('register') }}">Register</a>
@endguest

{{-- With specific guard --}}
@auth('admin')
    <a href="{{ route('admin.dashboard') }}">Admin Panel</a>
@endauth

@env

Проверка окружения:

@env('local')
    <div class="debug-bar">
        Debug info: {{ App::environment() }}
    </div>
@endenv

@env(['staging', 'production'])
    <script src="{{ asset('js/analytics.js') }}"></script>
@endenv

@production

@production
    <script src="https://analytics.example.com/track.js"></script>
@endproduction

@hasSection

@hasSection('sidebar')
    <div class="col-md-3">
        @yield('sidebar')
    </div>
    <div class="col-md-9">
        @yield('content')
    </div>
@else
    <div class="col-md-12">
        @yield('content')
    </div>
@endif

Директива @switch

@switch($status)
    @case('pending')
        <span class="badge badge-warning">Pending</span>
        @break

    @case('approved')
        <span class="badge badge-success">Approved</span>
        @break

    @case('rejected')
        <span class="badge badge-danger">Rejected</span>
        @break

    @default
        <span class="badge badge-secondary">Unknown</span>
@endswitch

Циклы

@for

@for ($i = 0; $i < 10; $i++)
    <p>Current value: {{ $i }}</p>
@endfor

@foreach

@foreach ($users as $user)
    <li>{{ $user->name }} ({{ $user->email }})</li>
@endforeach

{{-- With key --}}
@foreach ($user->skills as $index => $skill)
    <span>{{ $index + 1 }}. {{ $skill }}</span>
@endforeach

@forelse - цикл с обработкой пустой коллекции

@forelse - одна из самых полезных директив Blade. Она объединяет @foreach и проверку на пустоту:

@forelse ($orders as $order)
    <tr>
        <td>{{ $order->id }}</td>
        <td>{{ $order->total }}</td>
        <td>{{ $order->status }}</td>
    </tr>
@empty
    <tr>
        <td colspan="3">No orders found.</td>
    </tr>
@endforelse

@while

@while ($condition)
    <p>Looping...</p>
@endwhile

Переменная $loop

Внутри @foreach и @forelse доступна магическая переменная $loop:

@foreach ($users as $user)
    <div class="user-card {{ $loop->even ? 'bg-gray-100' : 'bg-white' }}">
        <span>{{ $loop->iteration }}. {{ $user->name }}</span>

        @if ($loop->first)
            <span class="badge">First</span>
        @endif

        @if ($loop->last)
            <span class="badge">Last</span>
        @endif
    </div>
@endforeach

Полный список свойств $loop:

$loop->index       // Current iteration index (0-based)
$loop->iteration   // Current iteration (1-based)
$loop->remaining   // Iterations remaining
$loop->count       // Total items in collection
$loop->first       // Is first iteration?
$loop->last        // Is last iteration?
$loop->even        // Is even iteration?
$loop->odd         // Is odd iteration?
$loop->depth       // Nesting level of the loop
$loop->parent      // Parent's $loop variable (nested loops)

Пример вложенных циклов:

@foreach ($categories as $category)
    <h3>{{ $category->name }}</h3>

    @foreach ($category->products as $product)
        <p>
            Category {{ $loop->parent->iteration }},
            Product {{ $loop->iteration }} of {{ $loop->count }}:
            {{ $product->name }}
        </p>
    @endforeach
@endforeach

@continue и @break

@foreach ($users as $user)
    @if ($user->is_banned)
        @continue
    @endif

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

    @if ($user->is_admin)
        @break
    @endif
@endforeach

{{-- Short syntax --}}
@foreach ($users as $user)
    @continue($user->is_banned)

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

    @break($loop->iteration > 10)
@endforeach

Директива @class

@class условно объединяет CSS-классы. Принимает массив, где ключ - класс, значение - условие:

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

<div @class([
    'p-4 rounded-lg',
    'bg-blue-500 text-white' => $isActive,
    'bg-red-500' => $hasError,
    'opacity-50 cursor-not-allowed' => $isDisabled,
])>
    Content
</div>

{{-- Renders: <div class="p-4 rounded-lg bg-blue-500 text-white"> --}}

Реальный пример с навигацией:

@foreach ($menuItems as $item)
    <a
        href="{{ $item->url }}"
        @class([
            'nav-link px-3 py-2',
            'active font-bold' => request()->routeIs($item->route),
            'text-muted' => !$item->is_enabled,
        ])
    >
        {{ $item->title }}
    </a>
@endforeach

Директива @style

Аналогично @class, но для inline-стилей:

<div @style([
    'background-color: red' => $hasError,
    'font-weight: bold' => $isImportant,
    'display: none' => !$isVisible,
])>
    Content
</div>

Директивы форм: @checked, @selected, @disabled, @readonly, @required

Эти директивы упрощают работу с HTML-формами:

@checked

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

{{-- Renders: <input type="checkbox" name="remember" value="1" checked /> --}}

{{-- Multiple checkboxes --}}
@foreach ($roles as $role)
    <label>
        <input
            type="checkbox"
            name="roles[]"
            value="{{ $role->id }}"
            @checked(in_array($role->id, old('roles', $user->roles->pluck('id')->toArray())))
        />
        {{ $role->name }}
    </label>
@endforeach

@selected

<select name="country">
    @foreach ($countries as $code => $name)
        <option value="{{ $code }}" @selected(old('country', $user->country) === $code)>
            {{ $name }}
        </option>
    @endforeach
</select>

@disabled

<button type="submit" @disabled($form->isProcessing)>
    Submit
</button>

<input
    type="text"
    name="email"
    value="{{ $user->email }}"
    @disabled(!$user->canChangeEmail())
/>

@readonly и @required

<input
    type="text"
    name="username"
    value="{{ $user->username }}"
    @readonly($user->hasVerifiedUsername())
/>

<input
    type="email"
    name="email"
    @required($isNewUser)
/>

Комментарии Blade

{{-- This is a Blade comment --}}
{{-- It will NOT appear in the rendered HTML --}}

<!-- This is an HTML comment -->
<!-- It WILL appear in the rendered HTML -->

@php директива

@php
    $totalPrice = $items->sum('price');
    $discountedPrice = $totalPrice * (1 - $discount / 100);
    $formattedPrice = number_format($discountedPrice, 2, '.', ' ');
@endphp

<p>Total: {{ $formattedPrice }} USD</p>

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

@include

{{-- Basic include --}}
@include('partials.header')

{{-- Include with additional data --}}
@include('partials.alert', ['type' => 'success', 'message' => 'Saved!'])

{{-- Include if exists --}}
@includeIf('custom.sidebar')

{{-- Include when condition is true --}}
@includeWhen($user->isAdmin(), 'admin.tools')

{{-- Include unless condition is true --}}
@includeUnless($user->isBanned(), 'partials.actions')

{{-- Include first existing view --}}
@includeFirst(['custom.header', 'default.header'])

@each - итерация с include

{{-- Render 'partials.user-card' for each user, passing $user as 'user' --}}
@each('partials.user-card', $users, 'user')

{{-- With empty state template --}}
@each('partials.user-card', $users, 'user', 'partials.no-users')

@once

Выводит содержимое только один раз, даже если шаблон рендерится в цикле:

@foreach ($products as $product)
    @once
        <script src="{{ asset('js/product-gallery.js') }}"></script>
    @endonce

    <div class="product">{{ $product->name }}</div>
@endforeach

@pushOnce

@pushOnce('scripts')
    <script src="{{ asset('js/datepicker.js') }}"></script>
@endPushOnce

Stacks

Стеки позволяют пушить контент в именованные стеки, которые рендерятся в другом месте:

{{-- In layout --}}
<head>
    @stack('styles')
</head>
<body>
    @yield('content')
    @stack('scripts')
</body>

{{-- In child view --}}
@push('styles')
    <link rel="stylesheet" href="{{ asset('css/custom.css') }}">
@endpush

@push('scripts')
    <script src="{{ asset('js/chart.js') }}"></script>
@endpush

{{-- Prepend to stack --}}
@prepend('scripts')
    <script src="{{ asset('js/core.js') }}"></script>
@endprepend

Sections и Layouts

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

{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html>
<head>
    <title>@yield('title', config('app.name'))</title>
    @stack('styles')
</head>
<body>
    @section('sidebar')
        <nav>Default Sidebar</nav>
    @show

    <main>
        @yield('content')
    </main>

    @stack('scripts')
</body>
</html>

{{-- resources/views/pages/home.blade.php --}}
@extends('layouts.app')

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

@section('sidebar')
    @parent {{-- Include parent's sidebar content --}}
    <nav>Additional Sidebar Content</nav>
@endsection

@section('content')
    <h1>Welcome</h1>
    <p>This is the home page.</p>
@endsection

::alert{type="info"} @yield vs @section/@show: @yield просто выводит содержимое секции. @section/@show определяет секцию с содержимым по умолчанию, которое можно расширить через @parent в дочернем шаблоне. ::


Проверь себя

Что выведет директива @class(['btn', 'btn-primary' => true, 'btn-disabled' => false, 'mt-4'])?

Какое свойство переменной $loop используется для получения общего количества элементов в коллекции?

Как в Blade оставить синтаксис {{ }} для JavaScript-фреймворка (Vue.js), не обрабатывая его как Blade-выражение?

Чем отличается @yield('content') от @section('content') @show?

Что произойдёт при использовании {{ $userInput }} если переменная содержит '<script>alert(1)</script>'?