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 EnsureTokenIsValid

Khi 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

By HNK

Leave a Reply

Your email address will not be published. Required fields are marked *