Контролери — Laravel
- Вступ
- Написання контролерів
- Middleware контролера
- Ресурсні контролери
- Впровадження залежностей і контролери
Вступ
Замість того щоб описувати всю логіку обробки запитів як замикання у файлах маршрутів, ви можете організувати цю поведінку за допомогою класів-«контролерів». Контролери здатні згрупувати повʼязану логіку обробки запитів в одному класі. Наприклад, клас 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-контролер, скориставшись опцією --invokable Artisan-команди make:controller:
php artisan make:controller ProvisionServer --invokable
Примітка Заготовки (stubs) контролерів можна налаштувати за допомогою публікації заготовок.
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, не пишучи цілий клас 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, призначені методам, буде обʼєднано з 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
Генерація form request-класів
Ви можете передати опцію --requests під час генерації ресурсного контролера, щоб указати Artisan згенерувати класи form request для методів збереження й оновлення контролера:
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);
Примітка Памʼятайте про те, щоб ваші контролери лишалися сфокусованими. Якщо ви регулярно потребуєте методів поза типовим набором ресурсних дій, розгляньте можливість розділити ваш контролер на два менші контролери.
Одиничні (singleton) ресурсні контролери
Іноді ваш застосунок матиме ресурси, які можуть мати лише один екземпляр. Наприклад, «профіль» користувача можна редагувати або оновлювати, але користувач не може мати більш ніж один «профіль». Так само зображення може мати одну «мініатюру». Такі ресурси називаються «одиничними ресурсами» (singleton resources), тобто може існувати один і лише один екземпляр ресурсу. У таких сценаріях ви можете зареєструвати «одиничний» ресурсний контролер:
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');
}
}
Виправити терміни, дописати розділ або взяти нову главу може кожен. Термінологію узгоджуємо в глосарії, щоб переклад лишався однорідним.