Blade: шаблоны
Что такое Blade
Blade — шаблонизатор Laravel. Пишешь HTML с вставками PHP через директивы. Blade компилируется в чистый PHP и кешируется.
Плюсы:
- Чистый синтаксис —
{{ $var }},@if. - Автоэкранирование — защита от XSS.
- Наследование — layouts и sections.
- Компоненты — переиспользуемые блоки.
- Компиляция в PHP — быстро.
Где лежат
resources/views/, расширение .blade.php.
Пример: resources/views/posts/index.blade.php → имя posts.index.
Точка = подпапка: posts/index.blade.php = posts.index.
Простой шаблон
<!DOCTYPE html>
<html>
<head><title>Привет</title></head>
<body>
<h1>Привет, {{ $name }}</h1>
</body>
</html>
Рендер из контроллера:
return view('hello', ['name' => 'Мир']);
return view('hello', compact('name'));
Вывод данных
{{ }} — вывод с экранированием:
{{ $name }}
{{ $user->email }}
{{ 2 + 2 }}
{{ $items[0] }}
Что экранируется:
<→<>→>&→&"→"
Защита от XSS.
{!! !!} — без экранирования:
{!! $trustedHtml !!}
⚠️ Только для доверенных данных. Иначе — XSS.
{{ $name ?? 'Гость' }} — с дефолтом.
Условия
if / elseif / else:
@if ($user->isAdmin())
<p>Админ</p>
@elseif ($user->isModerator())
<p>Модератор</p>
@else
<p>Пользователь</p>
@endif
unless:
@unless ($user->isAdmin())
<p>Не админ</p>
@endunless
isset / empty:
@isset($user)
{{ $user->name }}
@endisset
@empty($posts)
<p>Постов нет</p>
@endempty
auth / guest:
@auth
<p>Привет, {{ auth()->user()->name }}</p>
@endauth
@guest
<a href="/login">Войти</a>
@endguest
switch:
@switch($status)
@case('active')
<p>Активен</p>
@break
@case('pending')
<p>Ожидает</p>
@break
@default
<p>Неизвестно</p>
@endswitch
Циклы
foreach:
@foreach ($posts as $post)
<article>{{ $post->title }}</article>
@endforeach
forelse:
@forelse ($posts as $post)
<p>{{ $post->title }}</p>
@empty
<p>Постов нет</p>
@endforelse
for:
@for ($i = 0; $i < 5; $i++)
<p>{{ $i }}</p>
@endfor
while:
@while ($condition)
<p>...</p>
@endwhile
break / continue:
@foreach ($items as $item)
@continue($item->hidden)
@break($item->stop)
<p>{{ $item->name }}</p>
@endforeach
Переменная $loop
Внутри @foreach доступна $loop:
@foreach ($posts as $post)
{{ $loop->index }} {{-- с 0 --}}
{{ $loop->iteration }} {{-- с 1 --}}
{{ $loop->remaining }}
{{ $loop->count }} {{-- всего --}}
{{ $loop->first ? 'первый' : '' }}
{{ $loop->last ? 'последний' : '' }}
{{ $loop->even ? 'чётный' : '' }}
{{ $loop->odd ? 'нечётный' : '' }}
@if ($loop->first)
<hr>
@endif
@endforeach
Комментарии
{{-- Этот комментарий не попадёт в HTML --}}
Обычный HTML-комментарий <!-- --> попадёт.
Layouts
Layout — каркас страницы.
resources/views/layouts/app.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>@yield('title', 'Мой сайт')</title>
@stack('styles')
</head>
<body>
<header>
@include('partials.header')
</header>
<main>
@yield('content')
</main>
<footer>
@include('partials.footer')
</footer>
@stack('scripts')
</body>
</html>
Директивы:
@yield('name')— слот для контента.@include('view')— вставить другой шаблон.@stack('name')— стек для push.
Дочерний шаблон:
@extends('layouts.app')
@section('title', 'Главная')
@push('styles')
<link rel="stylesheet" href="/css/home.css">
@endpush
@section('content')
<h1>Добро пожаловать</h1>
<p>Контент главной</p>
@endsection
@push('scripts')
<script src="/js/home.js"></script>
@endpush
Что делает:
@extends— наследует layout.@section('title', 'Главная')— короткий (инлайн).@section('content') ... @endsection— длинный блок.@push— добавить в стек.
Компоненты
Blade-компонент — переиспользуемый кусок.
Создать:
php artisan make:component Alert
Два файла:
app/View/Components/Alert.php— логика.resources/views/components/alert.blade.php— шаблон.
alert.blade.php:
<div {{ $attributes->merge(['class' => 'alert alert-' . $type]) }}>
{{ $slot }}
</div>
Использование:
<x-alert type="success">
Сохранено!
</x-alert>
Что здесь:
$slot— содержимое тега.$attributes— атрибуты.merge— слияние классов.
Компонент с данными
app/View/Components/Alert.php:
namespace App\View\Components;
use Illuminate\View\Component;
class Alert extends Component
{
public function __construct(
public string $type = 'info',
) {}
public function render()
{
return view('components.alert');
}
}
Использование:
<x-alert type="success">Готово</x-alert>
Именованные слоты
<div class="card">
<header>{{ $header }}</header>
<main>{{ $slot }}</main>
<footer>{{ $footer }}</footer>
</div>
Использование:
<x-card>
<x-slot:header>
Заголовок
</x-slot>
Основной контент
<x-slot:footer>
Футер
</x-slot>
</x-card>
Другие директивы
csrf:
<form method="POST">
@csrf
<!-- поля -->
</form>
method:
<form method="POST">
@csrf
@method('PUT')
<!-- поля -->
</form>
PHP:
@php
$total = $items->sum('price');
@endphp
<p>{{ $total }}</p>
⚠️ Не злоупотребляй — логика в контроллере.
errors:
@if ($errors->any())
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
@error('title')
<p>{{ $message }}</p>
@enderror
old:
<input name="title" value="{{ old('title') }}">
Практика
layouts/app.blade.php:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>@yield('title', config('app.name'))</title>
@stack('styles')
</head>
<body>
@include('partials.nav')
<main class="container">
@if (session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
@yield('content')
</main>
@include('partials.footer')
@stack('scripts')
</body>
</html>
posts/index.blade.php:
@extends('layouts.app')
@section('title', 'Посты')
@section('content')
<h1>Все посты</h1>
@forelse ($posts as $post)
<article>
<h2>
<a href="{{ route('posts.show', $post) }}">
{{ $post->title }}
</a>
</h2>
<p>{{ $post->excerpt }}</p>
<small>
{{ $post->created_at->format('d.m.Y') }} —
{{ $post->user->name }}
</small>
</article>
@empty
<p>Постов нет</p>
@endforelse
{{ $posts->links() }}
@endsection
Частые ошибки
1. Забыли @endsection:
ParseError: unexpected end of file
2. Точка вместо слеша:
@include('partials/header') // ok
@include('partials.header') // ok
@include('partials::header') // namespace
3. {{ }} в JS:
<script>
const name = "{{ $name }}"; // опасно, если есть кавычки
</script>
Решение: @json($name).
4. @yield без default:
<title>@yield('title')</title> // может быть пусто
Решение: @yield('title', 'Сайт').
5. @section дважды:
@section('content')
A
@endsection
@section('content')
B // заменит A
@endsection
Решение: @parent.
Итог
{{ }}— вывод с экранированием.{!! !!}— без экранирования (осторожно).@if,@foreach,@forelse— управляющие.$loop— информация о цикле.@extends,@section,@yield— наследование.<x-...>— компоненты.@csrf,@method— формы.old(),@error— работа с ошибками.
Дальше — миграции.