- Введение
- Создание контроллеров
- 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
{
/**
* Показать профиль указанного пользователя.
*/
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, и параметры маршрута будут переданы в метод.
Контроллеры не обязаны наследоваться от базового класса. Однако в этом случае вы не получите доступ к удобным функциям, таким как методы middleware и authorize.
#Контроллеры с одним действием
Если действие контроллера особенно сложное, может быть удобно выделить отдельный класс контроллера для этого единственного действия. Для этого вы можете определить единственный метод __invoke в контроллере:
<?php
namespace App\Http\Controllers;
class ProvisionServer extends Controller
{
/**
* Настроить новый веб-сервер.
*/
public function __invoke()
{
// ...
}
}
При регистрации маршрутов для контроллеров с одним действием не нужно указывать метод контроллера. Вместо этого достаточно передать имя контроллера в маршрутизатор:
use App\Http\Controllers\ProvisionServer;
Route::post('/server', ProvisionServer::class);
Вы можете сгенерировать invokable-контроллер, используя опцию --invokable команды make:controller Artisan:
php artisan make:controller ProvisionServer --invokable
Шаблоны контроллеров можно настроить с помощью публикации stub-файлов.
#Middleware контроллеров
Middleware можно назначать маршрутам контроллера в файлах маршрутов:
Route::get('profile', [UserController::class, 'show'])->middleware('auth');
Или вы можете указать middleware внутри конструктора контроллера. Используя метод middleware в конструкторе, вы можете назначить middleware для действий контроллера:
class UserController extends Controller
{
/**
* Создать новый экземпляр контроллера.
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('log')->only('index');
$this->middleware('subscribed')->except('store');
}
}
Контроллеры также позволяют регистрировать middleware с помощью замыкания. Это удобный способ определить inline middleware для одного контроллера без создания отдельного класса middleware:
use Closure;
use Illuminate\Http\Request;
$this->middleware(function (Request $request, Closure $next) {
return $next($request);
});
#Ресурсные контроллеры
Если рассматривать каждую модель Eloquent в вашем приложении как «ресурс», обычно для каждого ресурса выполняется одинаковый набор действий. Например, представим, что в вашем приложении есть модели Photo и Movie. Скорее всего, пользователи смогут создавать, просматривать, обновлять или удалять эти ресурсы.
Из-за такой типичной задачи ресурсный роутинг Laravel назначает стандартные маршруты создания, чтения, обновления и удаления («CRUD») контроллеру одной строкой кода. Для начала можно использовать опцию --resource команды make:controller Artisan, чтобы быстро создать контроллер для обработки этих действий:
php artisan make:controller PhotoController --resource
Эта команда создаст контроллер в app/Http/Controllers/PhotoController.php. Контроллер будет содержать методы для каждого из доступных операций с ресурсом. Далее вы можете зарегистрировать ресурсный маршрут, указывающий на контроллер:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class);
Это одно объявление маршрута создаст несколько маршрутов для обработки различных действий с ресурсом. Сгенерированный контроллер уже будет содержать заглушки для каждого из этих действий. Помните, что вы всегда можете быстро получить обзор маршрутов вашего приложения, выполнив команду route:list Artisan.
Вы даже можете зарегистрировать сразу несколько ресурсных контроллеров, передав массив в метод resources:
Route::resources([
'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 при определении вложенного ресурса, вы можете включить автоматическое ограничение и указать, по какому полю должен извлекаться дочерний ресурс. Подробнее об этом см. в разделе область действия ресурсных маршрутов.
#Мелкое вложение
Часто нет необходимости указывать и ID родителя, и ID дочернего ресурса в URI, так как ID дочернего ресурса уже уникален. При использовании уникальных идентификаторов, таких как автоинкрементные первичные ключи, вы можете применить «мелкое вложение»:
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 implicit model binding может автоматически ограничивать вложенные привязки так, чтобы дочерняя модель гарантированно принадлежала родительской. Используя метод scoped при определении вложенного ресурса, вы можете включить автоматическое ограничение и указать, по какому полю должен извлекаться дочерний ресурс:
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\RouteServiceProvider:
/**
* Определите привязки моделей к маршрутам, фильтры паттернов и т. п.
*/
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);
Помните, что контроллеры должны оставаться сфокусированными. Если вам регулярно нужны методы вне типичного набора действий ресурса, рассмотрите возможность разделения контроллера на два меньших.
#Синглтон ресурсные контроллеры
Иногда в приложении есть ресурсы, которые могут иметь только один экземпляр. Например, «профиль» пользователя можно редактировать или обновлять, но у пользователя не может быть более одного «профиля». Аналогично, у изображения может быть только один «thumbnail». Такие ресурсы называются «синглтон ресурсами», то есть существует ровно один экземпляр ресурса. В таких случаях можно зарегистрировать «синглтон» ресурсный контроллер:
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();
#Внедрение зависимостей и контроллеры
#Внедрение через конструктор
Laravel service container используется для разрешения всех контроллеров Laravel. В результате вы можете указывать любые зависимости, которые нужны вашему контроллеру, в его конструкторе. Объявленные зависимости автоматически разрешаются и внедряются в экземпляр контроллера:
<?php
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UserController extends Controller
{
/**
* Создать новый экземпляр контроллера.
*/
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
{
/**
* Сохранить нового пользователя.
*/
public function store(Request $request): RedirectResponse
{
$name = $request->name;
// Сохранить пользователя...
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
{
/**
* Обновить указанного пользователя.
*/
public function update(Request $request, string $id): RedirectResponse
{
// Обновить пользователя...
return redirect('/users');
}
}