Bài 5 — Routing, Controller và Middleware: chúng ta giữ nguyên trang TaskFlow đã xây, nhưng tách việc chọn URL, xử lý màn hình và gắn mã request thành ba trách nhiệm rõ ràng. Hãy hoàn thành bài 4 về cấu hình và logging trước, vì ví dụ tiếp tục dùng config/taskflow.php và log có điều kiện.
1. Vì sao cần tách closure hiện tại?
Closure của bài trước đủ tốt cho một màn hình nhỏ. Khi muốn dùng cùng cơ chế nhận diện request ở nhiều màn hình, sao chép code tạo mã vào từng route sẽ dễ bỏ sót. Trong bài này, route chỉ ánh xạ GET /taskflow tới controller; controller chuẩn bị dữ liệu cho view; middleware tạo mã và gắn nó vào response. Không phải mọi closure đều cần chuyển thành controller ngay, nhưng đây là thời điểm hợp lý để luyện cách phân chia.
GET /taskflow
→ AssignRequestId: tạo UUID, đặt request attribute
→ TaskFlowOverviewController: đọc config, ghi log, trả view
→ AssignRequestId: gắn X-Request-ID vào response
→ trình duyệt nhận HTML và header
Request attribute là dữ liệu nội bộ phía server, không phải query string do người dùng gửi. X-Request-ID chỉ giúp đối chiếu log, không phải token xác thực, quyền truy cập hay bằng chứng người gọi đáng tin cậy.
2. Tạo middleware
php artisan make:middleware AssignRequestId
Thay nội dung app/Http/Middleware/AssignRequestId.php bằng:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class AssignRequestId
{
public function handle(Request $request, Closure $next): Response
{
$id = (string) Str::uuid();
$request->attributes->set('request_id', $id);
$response = $next($request);
$response->headers->set('X-Request-ID', $id);
return $response;
}
}Phần trước $next chạy trên đường request đi vào; phần sau chạy khi nhận được response từ lớp bên trong. Ta luôn tạo mã mới phía server, không phản chiếu một header tùy ý của client. Không đưa IP, email hoặc token vào mã. Đây là UUID phục vụ chẩn đoán, chưa phải distributed trace lan truyền qua nhiều service.
Middleware này chỉ gắn lên route /taskflow. Vì thế không hứa mọi 404 của ứng dụng đều có header; request không khớp route có thể chưa đi qua middleware này. Nếu yêu cầu request ID trên mọi response, kể cả lỗi trước routing, cần thiết kế middleware toàn cục và cách render exception riêng. Đừng mở rộng phạm vi chỉ bằng một khẳng định trong tài liệu.
3. Tạo controller một hành động
php artisan make:controller TaskFlowOverviewController --invokable
Thay nội dung app/Http/Controllers/TaskFlowOverviewController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class TaskFlowOverviewController extends Controller
{
public function __invoke(Request $request): View
{
if (config('taskflow.overview_logging')) {
Log::info('taskflow.overview.viewed', [
'route' => 'taskflow.overview',
'request_id' => $request->attributes->get('request_id'),
]);
}
return view('taskflow.overview', [
'projectName' => config('taskflow.name'),
'milestone' => 'Request lifecycle',
]);
}
}__invoke cho phép dùng class controller làm một hành động. Request được Laravel cung cấp qua type hint; bạn không tự new Request hoặc đọc trực tiếp $_GET. Controller trả View, giữ nguyên dữ liệu và template của bài trước. Log nhận cùng request_id sẽ xuất hiện trong header, nhưng vẫn chỉ gồm các field được phép.
4. Nối route với controller và middleware
Nội dung routes/web.php ở mốc bài học này là:
<?php
use App\Http\Controllers\TaskFlowOverviewController;
use App\Http\Middleware\AssignRequestId;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/taskflow', TaskFlowOverviewController::class)
->middleware(AssignRequestId::class)
->name('taskflow.overview');Dùng class middleware trực tiếp nên chưa cần alias trong bootstrap/app.php. Khi một nhóm route cùng dùng middleware, có thể tổ chức group; đừng đăng ký đồng thời toàn cục và riêng route nếu không muốn chạy hai lần. Tên route taskflow.overview vẫn giữ nguyên nên test và liên kết dùng route() không cần đổi.
php artisan route:list --path=taskflow -vv
php artisan test
Route list phải hiển thị GET|HEAD, TaskFlowOverviewController và AssignRequestId. Đừng thêm route GET thứ hai cùng URI để "thử" controller; thay định nghĩa cũ. Với resource route trong tương lai, chú ý các path cố định như /tasks/create và tham số /tasks/{task}; ràng buộc tham số và thứ tự route cần được kiểm tra bằng test.
5. Kiểm thử mã request và cập nhật test log
Tạo tests/Feature/TaskFlowRequestIdTest.php:
<?php
namespace Tests\Feature;
use Illuminate\Support\Str;
use Tests\TestCase;
class TaskFlowRequestIdTest extends TestCase
{
public function test_request_id_is_server_generated_and_changes_per_request(): void
{
$first = $this->withHeader('X-Request-ID', 'untrusted-client-value')
->get('/taskflow')->assertOk();
$id = $first->headers->get('X-Request-ID');
$this->assertTrue(Str::isUuid($id));
$this->assertNotSame('untrusted-client-value', $id);
$second = $this->get('/taskflow')->assertOk();
$this->assertNotSame($id, $second->headers->get('X-Request-ID'));
}
}Test log của bài 4 đang kỳ vọng context chỉ có route nên cần cập nhật chủ động, không xóa test. Gán response từ lời gọi get() vào $response, rồi đổi phần kỳ vọng context thành:
['route' => 'taskflow.overview',
'request_id' => $response->headers->get('X-Request-ID')]
Điều này kiểm tra ID trong log khớp response, trong khi test mới kiểm tra UUID được tạo bởi server và khác nhau giữa hai request. Bộ test hiện tại đã pass 8 test, 18 assertions. Hai UUID khác nhau trong test không phải chứng minh toán học rằng không bao giờ trùng; đây là kiểm tra hồi quy cho cách sử dụng cơ chế tạo mã.
6. Quan sát bằng HTTP thực tế
Chạy server local của bài 2 rồi dùng curl (PowerShell có thể gọi curl.exe):
curl -i http://127.0.0.1:8000/taskflow
Tìm X-Request-ID ở response. Nếu bật TASKFLOW_OVERVIEW_LOGGING và config không còn cache cũ, tìm cùng mã trong channel log đã chọn. Không cần công khai log để người dùng tra cứu; bộ phận vận hành có thể nhận ID qua phản hồi lỗi và tìm trong hệ thống nội bộ.
7. Bài tập và lỗi cần tránh
Tạm bỏ middleware khỏi route và dự đoán test nào thất bại, sau đó khôi phục. Đổi path sang /workspace nhưng giữ tên route rồi kiểm tra nơi nào còn hardcode URL. Không dùng request ID để authorize; quyền truy cập TaskFlow sẽ được bổ sung trong phần authentication và policy.
Nếu class không tìm thấy, so namespace với đường dẫn và import. Nếu không có header, kiểm tra route:list trước khi sửa view. Nếu log không xuất hiện, kiểm tra cờ cấu hình và level như bài 4. Controller chưa cần truy vấn database; bài này không chứng minh chức năng quản lý task đã hoàn thành.
Tài liệu đối chiếu: Routing, Controllers, Middleware. Điều hướng: Bài 4 · Lộ trình. Bài tiếp theo tổ chức giao diện bằng Blade component và Vite.




Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.