Контроллеры: организация логики
Контроллер собирает связанную логику в один класс, чтобы маршруты оставались чистой картой, а код — структурированным.
Суть: вместо замыканий в
web.phpлогику выносят в классы-контроллеры изapp/Http/Controllers. Каждый метод обслуживает свой маршрут.
Писать всю логику прямо в маршрутах удобно лишь для пары страниц. Как только приложение растёт, файл web.php превращается в свалку. Контроллеры решают это: они группируют методы вокруг одной сущности. Например, ProductController содержит всё, что связано с товарами: показ списка, показ одного товара, создание, обновление, удаление.
Контроллер — это обычный PHP-класс, наследник базового Controller. Каждый его публичный метод — это действие (action), которое можно привязать к маршруту. Создаются контроллеры командой artisan, что экономит время и гарантирует правильную структуру.
Создание и привязка
# обычный контроллер
php artisan make:controller ProductController
# ресурсный контроллер со всеми CRUD-методами
php artisan make:controller ProductController --resource<?php
namespace App\Http\Controllers;
use App\Models\Product;
class ProductController extends Controller
{
// список товаров
public function index()
{
$products = Product::all();
return view('products.index', ['products' => $products]);
}
// один товар
public function show(Product $product)
{
return view('products.show', ['product' => $product]);
}
}Привязка маршрута к контроллеру выглядит так:
<?php
use App\Http\Controllers\ProductController;
Route::get('/products', [ProductController::class, 'index']);
Route::get('/products/{product}', [ProductController::class, 'show']);
// все CRUD-маршруты одной строкой
Route::resource('products', ProductController::class);Как работает под капотом
Когда маршрут указывает на [ProductController::class, 'index'], Laravel создаёт экземпляр контроллера через свой контейнер зависимостей и вызывает нужный метод. Если в сигнатуре метода объявлен тип (например, Product $product), фреймворк автоматически найдёт нужную запись в базе по параметру маршрута — это называется неявное связывание моделей.
Route::resource('products', ...) разворачивается в:
GET /products -> index() список
GET /products/create -> create() форма создания
POST /products -> store() сохранение
GET /products/{id} -> show() один товар
GET /products/{id}/edit -> edit() форма правки
PUT /products/{id} -> update() обновление
DELETE /products/{id} -> destroy() удаление
Семь методов из одной строки Route::resource — это и есть сила соглашений. Смоделируем диспетчеризацию «контроллер + действие» на Python.
Попробуй сам ▶
# Контроллер как набор действий
class ProductController:
def index(self): return 'Список товаров'
def show(self, id): return f'Товар {id}'
def store(self, data): return f'Создан: {data}'
def dispatch(controller, action, **params):
method = getattr(controller, action)
return method(**params)
c = ProductController()
print(dispatch(c, 'index'))
print(dispatch(c, 'show', id=7))
print(dispatch(c, 'store', data='Книга'))
Частые ошибки
- «Толстые» контроллеры. Сотни строк бизнес-логики в одном методе — признак, что пора выносить код в сервисы.
- Забыть импорт класса. Без
use App\Http\Controllers\ProductController;маршрут не найдёт контроллер. - Неправильное имя параметра. Для неявного связывания имя параметра маршрута должно совпадать с именем переменной в методе.
Best practices
- Один контроллер — одна сущность; для нетипичных действий делайте отдельный одно-методный контроллер (
__invoke). - Используйте
Route::resourceдля стандартного CRUD — меньше кода и единый стиль. - Тяжёлую логику выносите в сервис-классы, оставляя контроллер тонким координатором.
Контроллеры умеют принимать зависимости через внедрение (dependency injection). Если объявить в конструкторе или прямо в методе тип нужного класса — например, сервис OrderService $orders или объект Request $request — Laravel через контейнер сам создаст и передаст его. Это убирает ручное создание объектов и упрощает тестирование: в тесте легко подменить зависимость заглушкой. Для действий, которые не вписываются в стандартный CRUD, удобны одиночные контроллеры с магическим методом __invoke(): класс создаётся командой make:controller ProcessPayment --invokable, а в маршруте указывается просто имя класса без действия. Такой контроллер делает ровно одно дело и хорошо читается. Наконец, у ресурсных контроллеров можно ограничить набор методов через ->only([...]) или ->except([...]), если, скажем, удаление в вашем ресурсе не предусмотрено — это держит карту маршрутов честной и компактной.
Итог: контроллеры группируют логику вокруг сущностей и делают маршруты чистой картой. Вместе с ресурсными маршрутами они дают готовую структуру CRUD. Дальше посмотрим на middleware — фильтры запросов.