Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Питання на співбесіді: Blade

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

9 питань

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

Основні можливості:

{{-- Вивід зі змінних (екранується автоматично) --}}
<h1>{{ $post->title }}</h1>

{{-- Директиви замість PHP-конструкцій --}}
@if ($user)
    Вітаємо, {{ $user->name }}
@endif

@foreach ($posts as $post)
    <li>{{ $post->title }}</li>
@endforeach
  • {{ $value }} - екранує вивід (захист від XSS); {!! $html !!} виводить «сирий» HTML без екранування.
  • Директиви: @if, @foreach, @forelse, @auth, @can, @csrf, @vite.
  • Наслідування шаблонів через @extends/@section/@yield - спільний layout без дублювання.
  • Компоненти <x-alert /> - багаторазові елементи UI зі слотами та props.

Навіщо: чистіший і безпечніший за вкладений PHP, з повторним використанням розмітки.

Докладніше в документації: Blade-шаблони

У сучасному Laravel ассети збирає Vite. У шаблоні підключають директивою @vite:

@vite(['resources/css/app.css', 'resources/js/app.js'])

Під час розробки (npm run dev) Vite віддає файли з hot-reload; на продакшені (npm run build) - зібрані файли з хешами в імені для cache busting.

Для статичних файлів із public/ використовують хелпер asset('images/logo.png').

Докладніше в документації: Vite та ассети

Базовий layout оголошує «дірки» через @yield, дочірні шаблони їх заповнюють:

{{-- layouts/app.blade.php --}}
<html><body>
    <main>@yield('content')</main>
</body></html>
{{-- posts/show.blade.php --}}
@extends('layouts.app')

@section('content')
    <h1>{{ $post->title }}</h1>
@endsection

Навіщо: загальна розмітка (шапка, футер, підключення ассетів) описується один раз. Альтернатива - Blade-компоненти (<x-layout> зі слотами), які в нових проєктах часто витісняють @extends.

Докладніше в документації: Blade: наслідування шаблонів

Blade-компоненти - багаторазові елементи UI, які підключаються тегом:

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

Бувають:

  • Анонімні - лише файл resources/views/components/alert.blade.php.
  • Класові - php artisan make:component Alert, з PHP-класом для логіки.

Дані передаються атрибутами, контент - через слоти:

{{-- components/card.blade.php --}}
<div class="card">
    <h2>{{ $title }}</h2>
    {{ $slot }}
</div>

Чистіше за @include, бо мають явний інтерфейс (props) та інкапсуляцію.

Докладніше в документації: Blade-компоненти

{{ $value }} екранує вивід через htmlspecialchars, а {!! $value !!} виводить як є.

{{ $user->name }}      {{-- <b>Ада</b> покажеться як текст --}}
{!! $post->html !!}    {{-- теги спрацюють --}}

Чому це важливо: якщо користувач запише в ім'я <script>...</script>, {{ }} покаже це як текст, а {!! !!} виконає скрипт у браузері кожного, хто відкриє сторінку. Це XSS - викрадення сесії, дії від імені жертви.

Правила:

  • за замовчуванням - лише {{ }};
  • {!! !!} - для HTML, який згенерував сам застосунок (Markdown, перетворений безпечним рендерером, чи HTML, очищений HTML Purifier);
  • вміст від користувача в {!! !!} без очищення - ніколи.

Пастки, які екранування не закриває:

  • href="{{ $url }}" з javascript:alert(1) - екранування не забороняє схему, такі посилання валідують (url:http,https);
  • дані в <script> - для JavaScript беруть Js::from($data), а не {!! json_encode() !!}.

Для JavaScript-фреймворків, що теж використовують фігурні дужки, є @{{ name }} - Blade залишить вираз як є.

Докладніше в документації: Виведення даних

View Composer прив'язує дані до шаблону щоразу, коли той рендериться - щоб не дублювати передачу спільних даних у багатьох контролерах.

View::composer('partials.sidebar', function ($view) {
    $view->with('categories', Category::all());
});

Тепер будь-яке відображення partials.sidebar автоматично отримає $categories. Зручно для меню, лічильників, віджетів у layout. Реєструється у boot() сервіс-провайдера.

Докладніше в документації: View Composers

Обидва викликаються однаково - <x-alert type="error" />. Різниця в тому, де живе логіка.

Анонімний компонент - лише шаблон у resources/views/components:

{{-- components/alert.blade.php --}}
@props(['type' => 'info'])

<div {{ $attributes->class(['alert', 'alert-'.$type]) }}>
    {{ $slot }}
</div>

@props оголошує дані; решта атрибутів потрапляє в $attributes.

Компонент з класом - php artisan make:component Alert створює клас і шаблон:

class Alert extends Component
{
    public function __construct(public string $type = 'info', private Formatter $formatter) {}

    public function isUrgent(): bool
    {
        return $this->type === 'error';
    }

    public function render(): View
    {
        return view('components.alert');
    }
}

Публічні властивості й методи доступні в шаблоні, а конструктор отримує залежності з контейнера.

Коли що:

  • анонімний - для презентаційних компонентів: кнопки, картки, поля форм. Їх більшість;
  • з класом - коли потрібні залежності, обчислення чи метод shouldRender().

Спільне для обох: слоти ($slot, іменовані <x-slot:title>), злиття атрибутів (merge, class), @aware для даних батьківського компонента.

Докладніше в документації: Анонімні компоненти

Через стеки: макет оголошує місце, сторінки й компоненти додають туди вміст.

{{-- layouts/app.blade.php --}}
<head>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
    @stack('scripts')
</head>

{{-- сторінка зі графіком --}}
@push('scripts')
    <script src="https://cdn.example.com/chart.js" defer></script>
@endpush

Корисні варіанти:

  • @prepend - додати на початок стека;
  • @pushOnce('scripts') - додати лише раз, навіть якщо компонент з цим скриптом стоїть на сторінці десять разів;
  • @once ... @endonce - те саме для довільного блоку;
  • @pushIf($condition, 'scripts') - за умовою.

Чим це краще за @section: секцію перезаписує дочірній шаблон, а в стек можуть додавати кілька незалежних місць - сторінка, кожен компонент, вкладений шаблон.

Порядок: стек виводиться там, де стоїть @stack, а вміст збирається з усіх шаблонів, що рендеряться для сторінки. Компонент, відрендерений пізніше за @stack (наприклад, у Livewire-оновленні), уже не потрапить у head - для динамічних частин скрипти підключають інакше.

Докладніше в документації: Стеки

Blade компілює шаблони в звичайний PHP і кешує в storage/framework/views, тож сам синтаксис майже нічого не коштує. Повільними сторінки роблять інші речі.

Що зазвичай гальмує:

  • Запити з шаблонів. $post->author->name у циклі без with() - N+1, найчастіша причина. Model::preventLazyLoading() ловить це в розробці.
  • Сотні компонентів. Кожен компонент з класом - створення об'єкта через контейнер, злиття атрибутів, окремий шаблон. Таблиця на 500 рядків з п'ятьма компонентами в рядку - тисячі рендерів.
  • Вкладені Livewire-компоненти в циклі - кожен зі своїм станом і запитами.
  • @include у великих циклах і важкі обчислення в шаблоні.
  • Перший запит після деплою, якщо шаблони не прекомпільовані.

Як виміряти: Debugbar чи Telescope показують кількість і час шаблонів і запитів; профайлер (SPX, Blackfire, Xdebug) - де саме витрачено час. Корисно порівняти час до рендеру й сам рендер.

Що робити:

  • php artisan view:cache (входить в optimize) на деплої;
  • жадібно завантажувати дані в контролері й передавати в шаблон готове;
  • у великих списках - простіший HTML замість глибоко вкладених компонентів; анонімні компоненти дешевші за компоненти з класом;
  • кешувати фрагменти, що рідко змінюються (Cache::remember навколо view()->render()), або всю гостьову сторінку на рівні CDN;
  • пагінація замість виводу всього.

Після змін - знову виміряти: оптимізація без вимірювань часто прискорює не те.

Докладніше в документації: Оптимізація завантаження представлень