Контролери
Вступ
Замість визначати всю логіку обробки запитів замиканнями у файлах маршрутів, ви можете організувати цю поведінку класами-«контролерами». Контролери групують пов'язану логіку обробки запитів в одному класі. Наприклад, клас UserController може обробляти всі вхідні запити, пов'язані з користувачами: показ, створення, оновлення та видалення. За замовчуванням контролери зберігаються в каталозі app/Http/Controllers.
Написання контролерів
Базові контролери
Щоб швидко згенерувати новий контролер, виконайте команду Artisan make:controller. За замовчуванням усі контролери вашого застосунку зберігаються в каталозі app/Http/Controllers:
php artisan make:controller UserController
Погляньмо на приклад базового контролера. Контролер може мати будь-яку кількість публічних методів, які відповідатимуть на вхідні HTTP-запити:
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* Show the profile for a given user.
*/
public function show(string $id): View
{
return view('user.profile', [
'user' => User::findOrFail($id)
]);
}
}
Написавши клас контролера та метод, ви можете визначити маршрут до методу контролера так:
use App\Http\Controllers\UserController;
Route::get('/user/{id}', [UserController::class, 'show']);
Коли вхідний запит збігається з указаним URI маршруту, буде викликано метод show класу App\Http\Controllers\UserController, а параметри маршруту буде передано методу.
Контролери не зобов'язані успадковувати базовий клас. Утім, іноді зручно успадкувати базовий контролер із методами, спільними для всіх ваших контролерів.
Контролери однієї дії
Якщо дія контролера особливо складна, вам може бути зручно виділити для неї цілий клас контролера. Для цього визначте в контролері єдиний метод __invoke:
<?php
namespace App\Http\Controllers;
class ProvisionServer extends Controller
{
/**
* Provision a new web server.
*/
public function __invoke()
{
// ...
}
}
Реєструючи маршрути для контролерів однієї дії, вам не потрібно вказувати метод контролера. Натомість просто передайте маршрутизатору ім'я контролера:
use App\Http\Controllers\ProvisionServer;
Route::post('/server', ProvisionServer::class);
Ви можете згенерувати викликаний контролер за допомогою опції --invokable команди Artisan make:controller:
php artisan make:controller ProvisionServer --invokable
Заготовки контролерів можна налаштувати через публікацію заготовок.
Middleware контролерів
Middleware можна призначати маршрутам контролера у файлах маршрутів:
Route::get('/profile', [UserController::class, 'show'])->middleware('auth');
Або ж вам може бути зручно вказати middleware у самому класі контролера. Для цього ваш контролер має реалізувати інтерфейс HasMiddleware, який вимагає наявності статичного методу middleware. З цього методу ви можете повернути масив middleware, які слід застосувати до дій контролера:
<?php
namespace App\Http\Controllers;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
class UserController implements HasMiddleware
{
/**
* Get the middleware that should be assigned to the controller.
*/
public static function middleware(): array
{
return [
'auth',
new Middleware('log', only: ['index']),
new Middleware('subscribed', except: ['store']),
];
}
// ...
}
Ви також можете визначати middleware контролера як замикання - це зручний спосіб задати вбудований middleware, не пишучи цілого класу:
use Closure;
use Illuminate\Http\Request;
/**
* Get the middleware that should be assigned to the controller.
*/
public static function middleware(): array
{
return [
function (Request $request, Closure $next) {
return $next($request);
},
];
}
Атрибути middleware
Ви також можете призначати middleware контролерам за допомогою атрибутів PHP:
<?php
namespace App\Http\Controllers;
use Illuminate\Routing\Attributes\Controllers\Middleware;
#[Middleware('auth')]
#[Middleware('log', only: ['index'])]
#[Middleware('subscribed', except: ['store'])]
class UserController
{
// ...
}
Атрибути middleware можна розміщувати й на окремих методах контролера. Middleware, призначені методам, буде об'єднано з тими, що призначені на рівні класу:
<?php
namespace App\Http\Controllers;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Routing\Attributes\Controllers\Middleware;
#[Middleware('auth')]
class UserController
{
#[Middleware('log')]
#[Middleware('subscribed')]
public function index()
{
// ...
}
#[Middleware(static function (Request $request, Closure $next) {
// ...
return $next($request);
})]
public function store()
{
// ...
}
}
Щоб виключити middleware з контролера чи окремих його методів, скористайтеся атрибутом WithoutMiddleware. Аргументи only та except дозволяють обмежити атрибут рівня класу конкретними методами:
<?php
namespace App\Http\Controllers;
use App\Http\Middleware\EnsureTokenIsValid;
use Illuminate\Routing\Attributes\Controllers\WithoutMiddleware;
#[WithoutMiddleware('subscribed', except: ['index'])]
class UserController
{
#[WithoutMiddleware(EnsureTokenIsValid::class)]
public function index()
{
// ...
}
public function show()
{
// ...
}
}
Атрибути WithoutMiddleware рівня класу успадковуються дочірніми контролерами. Атрибут може прибирати лише middleware маршрутів і не діє на глобальні middleware.
Атрибути авторизації
Якщо ви авторизуєте дії контролера через політики, скористайтеся атрибутом Authorize як зручним скороченням для middleware can:
<?php
namespace App\Http\Controllers;
use App\Models\Comment;
use App\Models\Post;
use Illuminate\Routing\Attributes\Controllers\Authorize;
class CommentController
{
#[Authorize('create', [Comment::class, 'post'])]
public function store(Post $post)
{
// ...
}
#[Authorize('delete', 'comment')]
public function destroy(Comment $comment)
{
// ...
}
}
Перший аргумент - здатність (ability), яку ви хочете авторизувати. Другий - клас моделі, параметр маршруту чи параметри, які слід передати політиці.
Ресурсні контролери
Якщо уявляти кожну модель Eloquent у вашому застосунку як «ресурс», то типово над кожним ресурсом виконуються одні й ті самі набори дій. Наприклад, уявіть, що ваш застосунок містить моделі Photo і Movie. Найімовірніше, користувачі можуть створювати, читати, оновлювати чи видаляти ці ресурси.
Через цей поширений сценарій ресурсна маршрутизація Laravel призначає контролеру типові маршрути створення, читання, оновлення та видалення («CRUD») одним рядком коду. Щоб почати, скористаймося опцією --resource команди Artisan make:controller, аби швидко створити контролер для цих дій:
php artisan make:controller PhotoController --resource
Ця команда згенерує контролер за шляхом app/Http/Controllers/PhotoController.php. Контролер міститиме метод для кожної доступної операції над ресурсом. Далі ви можете зареєструвати ресурсний маршрут, що вказує на контролер:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class);
Це єдине оголошення маршруту створює кілька маршрутів для різних дій над ресурсом. Згенерований контролер уже матиме заготовки методів для кожної з цих дій. Пам'ятайте: ви завжди можете швидко переглянути маршрути свого застосунку командою Artisan route:list.
Ви навіть можете зареєструвати багато ресурсних контролерів одразу, передавши масив методу resources:
Route::resources([
'photos' => PhotoController::class,
'posts' => PostController::class,
]);
Метод softDeletableResources реєструє багато ресурсних контролерів, які всі використовують метод withTrashed:
Route::softDeletableResources([
'photos' => PhotoController::class,
'posts' => PostController::class,
]);
Дії, які обробляють ресурсні контролери
| Метод | URI | Дія | Ім'я маршруту |
|---|---|---|---|
| GET | /photos |
index | photos.index |
| GET | /photos/create |
create | photos.create |
| POST | /photos |
store | photos.store |
| GET | /photos/{photo} |
show | photos.show |
| GET | /photos/{photo}/edit |
edit | photos.edit |
| PUT/PATCH | /photos/{photo} |
update | photos.update |
| DELETE | /photos/{photo} |
destroy | photos.destroy |
Налаштування поведінки для відсутньої моделі
Зазвичай, якщо неявно прив'язану модель ресурсу не знайдено, генерується HTTP-відповідь 404. Утім, ви можете налаштувати цю поведінку, викликавши метод missing під час визначення ресурсного маршруту. Метод missing приймає замикання, яке буде викликано, якщо неявно прив'язану модель не вдалося знайти для будь-якого з маршрутів ресурсу:
use App\Http\Controllers\PhotoController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
Route::resource('photos', PhotoController::class)
->missing(function (Request $request) {
return Redirect::route('photos.index');
});
М'яко видалені моделі
Зазвичай неявна прив'язка моделей не отримує м'яко видалені моделі, а натомість повертає HTTP-відповідь 404. Однак ви можете вказати фреймворку дозволити м'яко видалені моделі, викликавши метод withTrashed під час визначення ресурсного маршруту:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class)->withTrashed();
Виклик withTrashed без аргументів дозволить м'яко видалені моделі для ресурсних маршрутів show, edit та update. Ви можете вказати підмножину цих маршрутів, передавши масив методу withTrashed:
Route::resource('photos', PhotoController::class)->withTrashed(['show']);
Вказання моделі ресурсу
Якщо ви використовуєте прив'язку моделей до маршрутів і хочете, щоб методи ресурсного контролера вказували тип екземпляра моделі, скористайтеся опцією --model під час генерації контролера:
php artisan make:controller PhotoController --model=Photo --resource
Генерація запитів форм
Ви можете передати опцію --requests під час генерації ресурсного контролера, щоб Artisan згенерував класи запитів форм для методів збереження та оновлення:
php artisan make:controller PhotoController --model=Photo --resource --requests
Часткові ресурсні маршрути
Оголошуючи ресурсний маршрут, ви можете вказати підмножину дій, які має обробляти контролер, замість повного набору типових дій:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class)->only([
'index', 'show'
]);
Route::resource('photos', PhotoController::class)->except([
'create', 'store', 'update', 'destroy'
]);
Ресурсні маршрути API
Оголошуючи ресурсні маршрути, які споживатимуть API, ви зазвичай захочете виключити маршрути, що показують HTML-шаблони, - як-от create та edit. Для зручності скористайтеся методом apiResource, який автоматично виключає ці два маршрути:
use App\Http\Controllers\PhotoController;
Route::apiResource('photos', PhotoController::class);
Ви можете зареєструвати багато ресурсних контролерів API одразу, передавши масив методу apiResources:
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\PostController;
Route::apiResources([
'photos' => PhotoController::class,
'posts' => PostController::class,
]);
Щоб швидко згенерувати ресурсний контролер API без методів create та edit, скористайтеся перемикачем --api під час виконання команди make:controller:
php artisan make:controller PhotoController --api
Вкладені ресурси
Іноді вам може знадобитися визначити маршрути до вкладеного ресурсу. Наприклад, ресурс фотографії може мати кілька коментарів, прикріплених до неї. Щоб вкласти ресурсні контролери, скористайтеся «крапковою» нотацією в оголошенні маршруту:
use App\Http\Controllers\PhotoCommentController;
Route::resource('photos.comments', PhotoCommentController::class);
Цей маршрут зареєструє вкладений ресурс, доступний за URI на кшталт такого:
/photos/{photo}/comments/{comment}
Обмеження області вкладених ресурсів
Можливість неявної прив'язки моделей Laravel може автоматично обмежувати область вкладених прив'язок так, щоб розв'язана дочірня модель гарантовано належала батьківській. Скориставшись методом scoped під час визначення вкладеного ресурсу, ви можете увімкнути автоматичне обмеження області, а також вказати Laravel, за яким полем слід отримувати дочірній ресурс. Докладніше про це дивіться в документації з обмеження області ресурсних маршрутів.
Поверхневе вкладення
Часто мати в URI одночасно ідентифікатори батька й дитини зовсім не обов'язково, адже ідентифікатор дитини вже унікальний. Використовуючи унікальні ідентифікатори на кшталт автоінкрементних первинних ключів для позначення моделей у сегментах URI, ви можете обрати «поверхневе вкладення»:
use App\Http\Controllers\CommentController;
Route::resource('photos.comments', CommentController::class)->shallow();
Це визначення маршруту створить такі маршрути:
| Метод | URI | Дія | Ім'я маршруту |
|---|---|---|---|
| GET | /photos/{photo}/comments |
index | photos.comments.index |
| GET | /photos/{photo}/comments/create |
create | photos.comments.create |
| POST | /photos/{photo}/comments |
store | photos.comments.store |
| GET | /comments/{comment} |
show | comments.show |
| GET | /comments/{comment}/edit |
edit | comments.edit |
| PUT/PATCH | /comments/{comment} |
update | comments.update |
| DELETE | /comments/{comment} |
destroy | comments.destroy |
Іменування ресурсних маршрутів
За замовчуванням усі дії ресурсного контролера мають ім'я маршруту; утім, ви можете перевизначити ці імена, передавши масив names із бажаними іменами:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class)->names([
'create' => 'photos.build'
]);
Іменування параметрів ресурсних маршрутів
За замовчуванням Route::resource створює параметри ваших ресурсних маршрутів на основі форми однини від імені ресурсу. Ви можете легко перевизначити це для кожного ресурсу окремо методом parameters. Масив, переданий методу parameters, має бути асоціативним масивом імен ресурсів та імен параметрів:
use App\Http\Controllers\AdminUserController;
Route::resource('users', AdminUserController::class)->parameters([
'users' => 'admin_user'
]);
Наведений вище приклад генерує такий URI для маршруту show цього ресурсу:
/users/{admin_user}
Обмеження області ресурсних маршрутів
Можливість неявної прив'язки моделей з обмеженням області Laravel може автоматично обмежувати область вкладених прив'язок так, щоб розв'язана дочірня модель гарантовано належала батьківській. Скориставшись методом scoped під час визначення вкладеного ресурсу, ви можете увімкнути автоматичне обмеження області, а також вказати Laravel, за яким полем слід отримувати дочірній ресурс:
use App\Http\Controllers\PhotoCommentController;
Route::resource('photos.comments', PhotoCommentController::class)->scoped([
'comment' => 'slug',
]);
Цей маршрут зареєструє вкладений ресурс з обмеженою областю, доступний за URI на кшталт такого:
/photos/{photo}/comments/{comment:slug}
Коли ви використовуєте неявну прив'язку з власним ключем як параметр вкладеного маршруту, Laravel автоматично обмежить запит так, щоб отримати вкладену модель через її батька, використовуючи домовленості для вгадування імені зв'язку на батьківській моделі. У цьому випадку припускатиметься, що модель Photo має зв'язок comments (множина від імені параметра маршруту), через який можна отримати модель Comment.
Локалізація URI ресурсів
За замовчуванням Route::resource створює URI ресурсів, використовуючи англійські дієслова та правила множини. Якщо вам потрібно локалізувати дієслова дій create та edit, скористайтеся методом Route::resourceVerbs. Це можна зробити на початку методу boot у App\Providers\AppServiceProvider вашого застосунку:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Route::resourceVerbs([
'create' => 'crear',
'edit' => 'editar',
]);
}
Плюралізатор Laravel підтримує кілька різних мов, які ви можете налаштувати за потреби. Щойно дієслова та мову множини налаштовано, реєстрація ресурсного маршруту на кшталт Route::resource('publicacion', PublicacionController::class) створить такі URI:
/publicacion/crear
/publicacion/{publicaciones}/editar
Доповнення ресурсних контролерів
Якщо вам потрібно додати до ресурсного контролера маршрути поза типовим набором ресурсних маршрутів, визначайте їх до виклику методу Route::resource; інакше маршрути, визначені методом resource, можуть ненавмисно взяти гору над вашими додатковими маршрутами:
use App\Http\Controller\PhotoController;
Route::get('/photos/popular', [PhotoController::class, 'popular']);
Route::resource('photos', PhotoController::class);
Пам'ятайте: тримайте свої контролери зосередженими. Якщо ви регулярно потребуєте методів поза типовим набором ресурсних дій, подумайте про поділ контролера на два менші.
Синглтон-ресурсні контролери
Іноді ваш застосунок матиме ресурси, які можуть існувати лише в одному екземплярі. Наприклад, «профіль» користувача можна редагувати чи оновлювати, але користувач не може мати більш ніж один «профіль». Так само зображення може мати одну «мініатюру». Такі ресурси називають «синглтон-ресурсами»: існує один і тільки один екземпляр ресурсу. У цих сценаріях ви можете зареєструвати «синглтон»-ресурсний контролер:
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
Route::singleton('profile', ProfileController::class);
Наведене вище визначення синглтон-ресурсу зареєструє такі маршрути. Як бачите, маршрути «створення» для синглтон-ресурсів не реєструються, а зареєстровані маршрути не приймають ідентифікатора, адже може існувати лише один екземпляр ресурсу:
| Метод | URI | Дія | Ім'я маршруту |
|---|---|---|---|
| GET | /profile |
show | profile.show |
| GET | /profile/edit |
edit | profile.edit |
| PUT/PATCH | /profile |
update | profile.update |
Синглтон-ресурси можна також вкладати у стандартний ресурс:
Route::singleton('photos.thumbnail', ThumbnailController::class);
У цьому прикладі ресурс photos отримає всі стандартні ресурсні маршрути; однак ресурс thumbnail буде синглтон-ресурсом із такими маршрутами:
| Метод | URI | Дія | Ім'я маршруту |
|---|---|---|---|
| GET | /photos/{photo}/thumbnail |
show | photos.thumbnail.show |
| GET | /photos/{photo}/thumbnail/edit |
edit | photos.thumbnail.edit |
| PUT/PATCH | /photos/{photo}/thumbnail |
update | photos.thumbnail.update |
Синглтон-ресурси зі створенням
Подекуди ви можете захотіти визначити маршрути створення та збереження для синглтон-ресурсу. Для цього викличте метод creatable під час реєстрації маршруту синглтон-ресурсу:
Route::singleton('photos.thumbnail', ThumbnailController::class)->creatable();
У цьому прикладі буде зареєстровано такі маршрути. Як бачите, для синглтон-ресурсів зі створенням реєструється також маршрут DELETE:
| Метод | URI | Дія | Ім'я маршруту |
|---|---|---|---|
| GET | /photos/{photo}/thumbnail/create |
create | photos.thumbnail.create |
| POST | /photos/{photo}/thumbnail |
store | photos.thumbnail.store |
| GET | /photos/{photo}/thumbnail |
show | photos.thumbnail.show |
| GET | /photos/{photo}/thumbnail/edit |
edit | photos.thumbnail.edit |
| PUT/PATCH | /photos/{photo}/thumbnail |
update | photos.thumbnail.update |
| DELETE | /photos/{photo}/thumbnail |
destroy | photos.thumbnail.destroy |
Якщо ви хочете, щоб Laravel зареєстрував для синглтон-ресурсу маршрут DELETE, але не реєстрував маршрутів створення чи збереження, скористайтеся методом destroyable:
Route::singleton(...)->destroyable();
Синглтон-ресурси API
Метод apiSingleton дозволяє зареєструвати синглтон-ресурс, яким керуватимуть через API, роблячи маршрути create та edit непотрібними:
Route::apiSingleton('profile', ProfileController::class);
Звісно, синглтон-ресурси API також можуть бути creatable, що зареєструє для ресурсу маршрути store та destroy:
Route::apiSingleton('photos.thumbnail', ProfileController::class)->creatable();
Middleware та ресурсні контролери
Laravel дозволяє призначати middleware всім або лише певним методам ресурсних маршрутів за допомогою методів middleware, middlewareFor і withoutMiddlewareFor. Ці методи дають тонкий контроль над тим, який middleware застосовується до кожної ресурсної дії.
Застосування middleware до всіх методів
Скористайтеся методом middleware, щоб призначити middleware всім маршрутам, згенерованим ресурсним чи синглтон-ресурсним маршрутом:
Route::resource('users', UserController::class)
->middleware(['auth', 'verified']);
Route::singleton('profile', ProfileController::class)
->middleware('auth');
Застосування middleware до конкретних методів
Скористайтеся методом middlewareFor, щоб призначити middleware одному чи кільком конкретним методам ресурсного контролера:
Route::resource('users', UserController::class)
->middlewareFor('show', 'auth');
Route::apiResource('users', UserController::class)
->middlewareFor(['show', 'update'], 'auth');
Route::resource('users', UserController::class)
->middlewareFor('show', 'auth')
->middlewareFor('update', 'auth');
Route::apiResource('users', UserController::class)
->middlewareFor(['show', 'update'], ['auth', 'verified']);
Метод middlewareFor можна також використовувати разом із синглтон-ресурсними контролерами та їхніми API-варіантами:
Route::singleton('profile', ProfileController::class)
->middlewareFor('show', 'auth');
Route::apiSingleton('profile', ProfileController::class)
->middlewareFor(['show', 'update'], 'auth');
Виключення middleware з конкретних методів
Скористайтеся методом withoutMiddlewareFor, щоб виключити middleware з конкретних методів ресурсного контролера:
Route::middleware(['auth', 'verified', 'subscribed'])->group(function () {
Route::resource('users', UserController::class)
->withoutMiddlewareFor('index', ['auth', 'verified'])
->withoutMiddlewareFor(['create', 'store'], 'verified')
->withoutMiddlewareFor('destroy', 'subscribed');
});
Впровадження залежностей і контролери
Впровадження через конструктор
Сервіс-контейнер Laravel використовується для розв'язання всіх контролерів Laravel. Завдяки цьому ви можете вказати типи будь-яких потрібних контролеру залежностей у його конструкторі. Оголошені залежності буде автоматично розв'язано й впроваджено в екземпляр контролера:
<?php
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UserController extends Controller
{
/**
* Create a new controller instance.
*/
public function __construct(
protected UserRepository $users,
) {}
}
Впровадження через метод
Крім впровадження через конструктор, ви можете вказувати типи залежностей у методах контролера. Поширений сценарій - впровадження екземпляра Illuminate\Http\Request у методи контролера:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Store a new user.
*/
public function store(Request $request): RedirectResponse
{
$name = $request->name;
// Store the user...
return redirect('/users');
}
}
Якщо ваш метод контролера також очікує вхідні дані з параметра маршруту, перелічуйте аргументи маршруту після інших залежностей. Наприклад, якщо ваш маршрут визначено так:
use App\Http\Controllers\UserController;
Route::put('/user/{id}', [UserController::class, 'update']);
Ви все одно можете вказати тип Illuminate\Http\Request і звертатися до параметра id, визначивши метод контролера так:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Update the given user.
*/
public function update(Request $request, string $id): RedirectResponse
{
// Update the user...
return redirect('/users');
}
}