Контроллеры
Что такое контроллер
Контроллер — класс, который обрабатывает запросы. Вместо замыканий в роутах — методы в контроллере.
Зачем:
- Структура — код по сущностям.
- Тестируемость — легко тестировать.
- DI — зависимости через конструктор.
- Middleware — на группу методов.
Создание
php artisan make:controller PostController
Файл app/Http/Controllers/PostController.php:
namespace App\Http\Controllers;
class PostController extends Controller
{
//
}
Базовый Controller — из App\Http\Controllers\Controller.
Базовый контроллер
class PostController extends Controller
{
public function index()
{
return 'Список постов';
}
public function show(string $id)
{
return "Пост #{$id}";
}
}
Каждый публичный метод — действие.
Привязка к роутам
Массивом:
use App\Http\Controllers\PostController;
Route::get('/posts', [PostController::class, 'index']);
Route::get('/posts/{id}', [PostController::class, 'show']);
Группировка:
Route::controller(PostController::class)->group(function () {
Route::get('/posts', 'index');
Route::get('/posts/{id}', 'show');
Route::post('/posts', 'store');
});
Внедрение зависимостей
Laravel сам разрешает зависимости:
public function show(PostService $service, string $id)
{
return $service->find($id);
}
PostService создаётся контейнером и передаётся.
В конструкторе:
class PostController extends Controller
{
public function __construct(
private PostService $service,
) {}
}
Dependency Injection в аргументах
Можно смешивать:
public function update(
Request $request, // Laravel подставит
PostService $service, // и это тоже
int $id, // из URL
) { }
Порядок: Laravel ищет классы в контейнере, остальное — из роута.
Типы ответов
Строка
return 'Hello';
Массив (JSON)
return ['status' => 'ok'];
View
return view('posts.index', ['posts' => $posts]);
return view('posts.show', compact('post'));
JSON
return response()->json(['status' => 'ok']);
return response()->json($data, 201);
return response()->json(['error' => 'Not found'], 404);
Пустой ответ
return response()->noContent(); // 204
Файл
return response()->download($path);
return response()->file($path);
Redirect
return redirect('/home');
return redirect()->route('posts.index');
return redirect()->action([PostController::class, 'index']);
return back();
return back()->withInput();
return back()->withErrors(['title' => 'Ошибка']);
return back()->with('success', 'Сохранено');
View с заголовками
return response()->view('posts.index', $data)
->header('X-Custom', 'value');
Middleware
В контроллере (старый способ):
public function __construct()
{
$this->middleware('auth');
$this->middleware('admin')->only(['destroy']);
$this->middleware('log')->except(['index']);
}
⚠️ В Laravel 11+ этот способ устарел. Используй роуты или атрибуты.
В роутах:
Route::middleware('auth')->group(function () {
Route::resource('posts', PostController::class);
});
Resource-контроллеры
Полный CRUD:
php artisan make:controller PostController --resource
Методы:
class PostController extends Controller
{
public function index() { } // GET /posts
public function create() { } // GET /posts/create
public function store(Request $r) { } // POST /posts
public function show(string $id) { } // GET /posts/{id}
public function edit(string $id) { } // GET /posts/{id}/edit
public function update(Request $r, string $id) { } // PUT/PATCH
public function destroy(string $id) { } // DELETE
}
Подключение:
Route::resource('posts', PostController::class);
Что создаст:
| Метод | URL | Действие | Имя |
|---|---|---|---|
| GET | /posts | index | posts.index |
| GET | /posts/create | create | posts.create |
| POST | /posts | store | posts.store |
| GET | /posts/{post} | show | posts.show |
| GET | /posts/{post}/edit | edit | posts.edit |
| PUT/PATCH | /posts/{post} | update | posts.update |
| DELETE | /posts/{post} | destroy | posts.destroy |
Ограничение:
Route::resource('posts', PostController::class)->only(['index', 'show']);
Route::resource('posts', PostController::class)->except(['destroy']);
API Resource
Без create и edit (в API нет форм):
php artisan make:controller Api/PostController --api
Route::apiResource('posts', PostController::class);
Создаёт 5 роутов: index, store, show, update, destroy.
Вложенные ресурсы
Route::resource('posts.comments', CommentController::class);
URL: /posts/{post}/comments/{comment}.
Обработка ошибок
Исключения:
if (!$post) {
abort(404);
}
if (!$request->user()->can('update', $post)) {
abort(403, 'Нет доступа');
}
abort_if / abort_unless:
abort_if($post->user_id !== auth()->id(), 403);
throw:
throw new \Illuminate\Database\Eloquent\ModelNotFoundException();
throw new \Symfony\Component\HttpKernel\Exception\HttpException(500);
Практика
namespace App\Http\Controllers;
use App\Models\Post;
use App\Services\PostService;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function __construct(
private PostService $service,
) {}
public function index()
{
$posts = Post::with('user')
->latest()
->paginate(15);
return view('posts.index', compact('posts'));
}
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
public function store(Request $request)
{
$data = $request->validate([
'title' => 'required|string|max:255',
'body' => 'required|string|min:10',
]);
$post = $request->user()->posts()->create($data);
return redirect()
->route('posts.show', $post)
->with('success', 'Пост создан');
}
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
$data = $request->validate([
'title' => 'sometimes|required|string|max:255',
'body' => 'sometimes|required|string|min:10',
]);
$post->update($data);
return back()->with('success', 'Обновлено');
}
public function destroy(Post $post)
{
$this->authorize('delete', $post);
$post->delete();
return redirect()
->route('posts.index')
->with('success', 'Удалено');
}
}
Частые ошибки
1. Забыли use:
Route::get('/x', [PostController::class, 'index']); // Class not found
2. Не тот namespace:
namespace App\Controllers; // неверно
namespace App\Http\Controllers; // верно
3. Внедряешь зависимость в приватный метод — не сработает.
4. $this->middleware() в конструкторе — устарело в Laravel 11+.
5. Забыли return:
public function index()
{
Post::all(); // return null
}
Итог
- Контроллер — класс обработчиков.
[Controller::class, 'method']— привязка к роуту.- DI — через конструктор и аргументы.
response()->json,view,redirect— ответы.- Resource — 7 методов CRUD.
abort()— бросить HTTP-ошибку.authorize()— проверка прав.
Дальше — Blade.