Middleware là một cơ chế giúp “inspect”, “filtering” các request đầu vào. Nói nôm na là khi có một request vào hệ thống, Middleware sẽ làm nhiệm vụ đánh giá, phân tích, thực hiện logic dựa trên các request đầu vào đó.
Ví dụ bạn cần xác thực thông tin người dùng khi hệ thống nhận được một request, lúc này bạn sẽ sử dụng Middleware như một công cụ hữu hiệu để làm việc này.
1. Tạo một Middleware
php artisan make:middleware EnsureTokenIsValidKhi này một class sẽ được tạo ra trong thư mục: app/Http/Middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureTokenIsValid
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->input('token') !== 'my-secret-token') {
return redirect('home');
}
return $next($request);
}
}Logic bạn muốn xử lý sẽ nằm trong hàm handle nhé.
Để ý đoạn $next($request) có nghĩa là tiếp tục cho request đi tiếp vào hệ thống. Dĩ nhiên bạn có thể thực hiện logic trước hoặc sau khi request đi sâu hơn vào hệ thống. Ví dụ:
Xử lý trước khi request đi có thể đi xa hơn
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class BeforeMiddleware
{
public function handle(Request $request, Closure $next): Response
{
// Perform action
return $next($request);
}
}Hoặc xử lý sau khi request đã qua Middleware và đi vào hệ thống
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class AfterMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// Perform action
return $response;
}
}2. Đăng ký Middleware với hệ thống
Đơn giản là vào class app/Http/Kernel.php rồi khai báo Middle trong biến $middleware
Để assign Middleware cho một route cụ thể thì các bạn dùng hàm middleware
use App\Http\Middleware\Authenticate;
Route::get('/profile', function () {
// ...
})->middleware(Authenticate::class);Dĩ nhiên, một route có thể có nhiều Middleware
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);Để cho tiện lợi, bạn có thể đặt name alisa cho Middleware.
Các bạn vào class app/Http/Kernel.php, khai báo alias name trong biến $middlewareAliases nhé
// Within App\Http\Kernel class...
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];Khi đã đặt alias name xong, các bạn có thể sử dụng, ví dụ như này
Route::get('/profile', function () {
// ...
})->middleware('auth');Bạn cũng có thể loại trừ (Exclude) Middleware ra khỏi route, ví dụ:
use App\Http\Middleware\EnsureTokenIsValid;
Route::middleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/', function () {
// ...
});
Route::get('/profile', function () {
// ...
})->withoutMiddleware([EnsureTokenIsValid::class]);
});Ví dụ trên cho thấy một tập hợp các routes đang có chung Middleware EnsureTokenIsValid, tuy nhiên route ‘/profile’ sẽ được coi như là không sử dụng Middleware này. Để Exclude Middleware các bạn dùng hàm withoutMiddleware nhé
Hoặc bạn cũng có thể chỉ định một tập hợp các routes không sử dụng Middleware EnsureTokenIsValid, ví dụ:
use App\Http\Middleware\EnsureTokenIsValid;
Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/profile', function () {
// ...
});
});3. Middleware group
Các bạn có thể nhóm tập hợp các Middleware thành một group. Ví dụ như trong class app/Http/Kernel.php, hãy nhìn biến $middlewareGroups
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];Để chỉ định route sử dụng group Middleware nào thì chỉ việc gọi hàm Route::middleware
Route::middleware(['web'])->group(function () {
// ...
});Hoặc dùng hàm middleware
Route::get('/', function () {
// ...
})->middleware('web');Lưu ý rằng group Middleware “web” và “api” mặc định được applied vào hệ thống.
Hãy xem class app/Providers/RouteServiceProvider.php
public function boot(): void
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
$this->routes(function () {
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});
}Giải thích đơn giản như sau, Middleware group ‘web’ được áp dụng cho toàn bộ routes nằm trong class routes/web.php
Tương tự Middleware group ‘api’ được áp dụng cho toàn bộ routes nằm trong class routes/api.php
4. Sắp xếp độ ưu tiên của Middleware
Để quyết định Middleware nào được áp dụng trước thì các bạn khai báo vào biến $middlewarePriority của class app/Http/Kernel.php, ví dụ
/**
* The priority-sorted list of middleware.
*
* This forces non-global middleware to always be in the given order.
*
* @var string[]
*/
protected $middlewarePriority = [
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Contracts\Session\Middleware\AuthenticatesSessions::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];5. Xử lý parameters trong Middleware
Một Middleware cũng có thể nhận parameters truyền vào, ví dụ
Route posts, sử dụng Middleware ‘role’, parameter truyền vào có giá trị là ‘publisher’
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role:publisher');Lấy ra parameter và thực hiện logic
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserHasRole
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string $role): Response
{
if (! $request->user()->hasRole($role)) {
// Redirect...
}
return $next($request);
}
}Nếu nhiều parameters thì hãy để cách nhau bơi dấu ‘,’
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role:editor,publisher');6. Terminable Middleware
Sau khi một lifecycle kết thúc, HTTP response được trả về cho browser, thì hàm terminate của Middleware sẽ tự động được gọi. Có nghĩa là, sau khi HTTP response được trả về, thì bạn có thể thực hiện tiếp một số logic nếu muốn
<?php
namespace Illuminate\Session\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class TerminatingMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
/**
* Handle tasks after the response has been sent to the browser.
*/
public function terminate(Request $request, Response $response): void
{
// ...
}
}Lưu ý: khi HTTP response được trả về, và hàm terminate được gọi, Laravel sẽ khởi tạo một instance mới của Middleware. Như vậy nếu muốn sử dụng same instance thì các bạn nên register với Container dưới dạng singleton nhé
use App\Http\Middleware\TerminatingMiddleware;
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(TerminatingMiddleware::class);
}Bài học này tới đây là hết rồi 🙂
Hẹn gặp lại các bạn ở bài sau: CSRF protection