Основы шаблонизатора 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 в дочернем шаблоне.
::