View::composer() — механізм Laravel, який автоматично передає дані у певні Blade-шаблони, незалежно від того, який контролер їх рендерить. Він спрацьовує щоразу, коли вказаний view готується до відображення.
Навіщо це потрібно
Типова ситуація: є шаблони layouts.app, layouts.partials.header, layouts.partials.footer — спільний каркас, який використовується на кожній сторінці. Дані для них завжди одні й ті самі (налаштування сайту, пункти меню, кольори, лого), і вони потрібні всюди.
Без View::composer залишається два поганих варіанти:
Варіант 1 — передавати в кожному контролері
class HomeController
{
public function index() {
return view('home', [
'siteName' => SiteSetting::singleton()->site_name,
'headerMenuItems' => Menu::location('header')?->menuItems ?? collect(),
'siteColors' => [...],
'posts' => Post::all(), // власне дані сторінки
]);
}
}
class AboutController
{
public function index() {
return view('about', [
'siteName' => SiteSetting::singleton()->site_name, // знову те саме
'headerMenuItems' => Menu::location('header')?->menuItems ?? collect(),
// ...знову копіпаста
]);
}
}
Дублювання у кожному контролері; додавання нового поля вимагає правок у десятках місць.
Варіант 2 — глобальні View::share()
View::share('siteName', ...);
View::share('headerMenuItems', ...);
Працює, але дані обчислюються на кожен запит, навіть коли layout не використовується (наприклад, на JSON-API-роутах).
View::composer розвʼязує обидві проблеми
View::composer(
['layouts.app', 'layouts.partials.header', 'layouts.partials.footer'],
function ($view) {
$view->with('siteName', ...);
// ...
}
);
Дані потрапляють у потрібні шаблони автоматично. Контролерам не треба про це думати — вони передають лише сторінкові дані. А якщо запит зовсім не рендерить ці шаблони — composer не виконається, ресурси не витратяться.
Як це працює всередині
Коли викликається view('layouts.app') чи Blade зустрічає @include('layouts.partials.header'):
- Laravel створює обʼєкт
Viewдля цього шаблону. - Перевіряє, чи є зареєстровані composers для нього.
- Виконує колбек composerʼа, передаючи туди
$view. - Через
$view->with(...)дані прикріплюються до view. - Blade рендерить шаблон, маючи доступ до всіх змінних.
Це відбувається прямо перед рендерингом, на кожен запит, де view використовується.
Розбір реального прикладу
public function boot(): void
{
View::composer(
['layouts.app', 'layouts.partials.header', 'layouts.partials.footer'],
function ($view): void {
$siteSettings = SiteSetting::singleton();
$view->with('headerMenuItems', Menu::location('header')?->menuItems ?? collect());
$view->with('footerMenuItems', Menu::location('footer')?->menuItems ?? collect());
$view->with('siteName', $siteSettings->site_name ?: config('app.name'));
$view->with('headerLogoUrl', filled($siteSettings->header_logo_path)
? Storage::disk('public')->url($siteSettings->header_logo_path)
: null);
$view->with('siteColors', [
'header_bg' => $siteSettings->header_bg_color ?: '#ffffff',
'footer_bg' => $siteSettings->footer_bg_color ?: '#ffffff',
'accent' => $siteSettings->accent_color ?: '#18181b',
// ...
]);
}
);
}
Composer зареєстрований для трьох шаблонів — основного layoutʼа та двох partials. Усередині дістаються налаштування сайту, підтягуються меню для шапки й футера, формуються URLʼи для лого та фавікона, збирається обʼєкт із кольорами для CSS-стилізації.
Тепер у будь-якому Blade-шаблоні, який extends layouts.app чи включає ці partials, доступні $siteName, $headerMenuItems, $siteColors тощо — без жодних зусиль з боку контролера.
Варіанти синтаксису
1. Closure — найпростіше
View::composer('partials.sidebar', function ($view) {
$view->with('recentPosts', Post::recent()->take(5)->get());
});
2. Окремий клас — для складнішої логіки
// app/View/Composers/SidebarComposer.php
namespace App\View\Composers;
use App\Repositories\PostRepository;
use Illuminate\View\View;
class SidebarComposer
{
public function __construct(private PostRepository $posts) {}
public function compose(View $view): void
{
$view->with('recentPosts', $this->posts->recent(5));
}
}
// у ServiceProvider
View::composer('partials.sidebar', SidebarComposer::class);
Перевага класу — інʼєкція залежностей, тестованість, чистіший код, якщо логіки багато.
3. Wildcards — кілька шаблонів за патерном
View::composer('admin.*', AdminMenuComposer::class);
View::composer('*', GlobalDataComposer::class); // взагалі всі view
Куди реєструвати
Стандартне місце — окремий App\Providers\ViewServiceProvider:
namespace App\Providers;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class ViewServiceProvider extends ServiceProvider
{
public function boot(): void
{
View::composer(['layouts.app', /* ... */], function ($view) {
// ...
});
}
}
І зареєструвати його в bootstrap/providers.php (Laravel 11+) або config/app.php (старіші версії). Можна писати і в AppServiceProvider, але з ростом проєкту краще виносити в окремий провайдер.
⚠️ Важливі поради
Кешуйте важкі запити
Composer запускається на кожному запиті, де рендериться layout — тобто практично завжди. Якщо налаштування рідко змінюються, кешуйте:
$siteSettings = Cache::rememberForever('site_settings',
fn() => SiteSetting::singleton()
);
$headerMenu = Cache::rememberForever('menu.header',
fn() => Menu::location('header')?->menuItems ?? collect()
);
І чистіть кеш в observerʼах моделей SiteSetting/Menu при оновленні. Це може суттєво розвантажити БД на трафікових сторінках.
Уникайте composerʼа на «*»
Composer на всі шаблони (View::composer('*', ...)) спрацьовує на кожен @include — а їх у Blade десятки. Завжди вказуйте конкретні шаблони.
Composer ≠ View Component
В Laravel 8+ є Blade Components із власним класом і методом render(). Для невеликих самодостатніх шматків UI (картка користувача, кнопка) краще використовувати компоненти. Composer лишається оптимальним для глобальних даних layoutʼа.
Підсумок
View::composer — це «гачок», який автоматично заливає певні дані у вказані Blade-шаблони, коли вони відображаються. Ідеальний use-case — спільний каркас сайту з меню, лого, кольорами, які не залежать від конкретної сторінки. Це звільняє контролери від рутини і централізує логіку формування layoutʼа в одному місці. Найважливіше — кешувати важкі запити всередині, щоб не перевантажувати БД на кожному запиті.