Tìm hiểu về Middleware trong Laravel

1.Mở đầu


Xin chào tất cả các bạn, đã rất lâu rồi kể từ lần cuối mình viết bài viết chia sẻ những kiến thức cơ bản về Laravel cũng như viết tiếp series Laravel up and running. Thì hôm nay mình đã trở lại với một bài viết mới và sẽ cố gắng cập nhật series chia sẻ kiến thức cơ bản cho các bạn mới tiếp cận với Laravel này đều hơn. Trong bài viết hôm này, chúng ta sẽ cùng tìm hiểu về khái niệm Middleware tronng web, nào chúng ta cùng bắt đầu.

2. Middleware dùng để làm gì ?


Trước khi đi vào việc tạo và sử dụng Middleware trong Laravel thì chúng ta sẽ cùng tìm hiểu về công dụng của nó qua. Giả sử chúng ta có một trang web xem phim trả phí với một vài tính năng như:

  • Xem danh sách các bộ phim mà hệ thống của bạn có
  • Xem một bộ phim
  • Download phim về máy

Và tương ứng chúng ta sẽ có vài chức năng như sau:

class MovieController extends Controller
{
    public function getList(Request $request)
    {
        // Lấy danh sách các bộ phim
    }

    public function watchMovie(Request $request)
    {
        // Xem một bộ phim
    }

    public function downloadMovie(Request $request)
    {
        // Download bộ phim bạn chọn
    }
}

Vì trang web chúng ta là trang web phải trả phí nên đồng nghĩa với việc người dùng phải là tài khoản đã đăng nhập rồi để có thể thực hiện chức năng nói trên. Với cách làm thông thường thì ta có thể thêm đoạn code kiểm trả xem người dùng đã đăng nhập chưa ở mỗi chức năng như sau:

class MovieController extends Controller
{
    public function getList(Request $request)
    {
        if (Auth::check()) {
            // Lấy danh sách các bộ phim
        }
    }

    public function watchMovie(Request $request)
    {
        if (Auth::check()) {
            // Xem một bộ phim
        }
    }

    public function downloadMovie(Request $request)
    {
        if (Auth::check()) {
           // Download bộ phim bạn chọn
        }
    }
}

Nếu trang web của bạn chỉ có 3 chức năng thì cũng ok không có vấn đề gì to tát lắm. Nhưng thử tưởng tượng bạn có đến 50 chức năng đều cần kiểm tra đăng nhập thì sao ? Bạn có thể vẫn try-hard bằng cách copy lại phần code kia cho cả 50 chức năng như trên và mọi thứ lại chạy đâu vào đó. Tuy nhiên một hôm đẹp trời, 50 tính năng này của bạn lại cần phải check xem nếu người dùng đã đăng nhập và trong tài khoảng trong phim phải có lớn hơn 50k VND thì sao.

Để giải quyết bài toán như nói trên thì chúng ta sẽ có khái niệm Web Middleware . Đơn giản nhất bạn có thể hiểu nó phần trong ứng dụng của bạn cho phép bạn gom toàn bộ 50 lần đoạn code nói trên về chung một nột chức năng duy nhất và có thể tái sử dụng lại. Middleware sẽ có vai trò đứng giữa các request từ người dùng đến hệ thống của bạn và kiểm tra xem nó có thỏa mãn các điều kiện mà bạn mong muốn trước khi có thể truy cập vào tính năng mà bạn cung câp:

  • Người dùng đã đăng nhập
  • Tài khoảng của người dùng có > 50k VND

Như bạn thấy tất cả những yêu cầu của người dùng đều phải đi qua đây trước khi có thể thực hiện các tính năng mà bạn cung cấp. Đồng nghĩa với việc ở đây, chúng ta có thể lầm bất cứ thứ gì mà chúng ta muốn với request này trước khi bạn cho nó tiếp tục di chuyển đến tính năng của bạn hoặc từ chối không cho nó đi tiếp.

3. Middleware trong Laravel


Để tạo mới một middleware, ta sử dụng cú pháp:

php artisan make:middleware [MiddlewareName]

ở đây giả sử mình sẽ tạo một middleware để kiểm tra trường hợp mà chúng ta nói trên như sau:

php artisan make:middleware VerifyAccountBalance

Sau khi bạn chạy lệnh trên thì Laravel sẽ tạo ra cho chúng ta một file mới nằm trong đường dẫn app/Http/Middleware/VerifyAccountBalance.php và có nội dung như sau:

<?php

namespace App\Http\Middleware;

use Closure;

class VerifyAccountBalance
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        return $next($request);
    }
}

Middleware mà chúng ta tạo ra ở đây chỉ có duy nhất một function là handle() và 2 tham số truyền vào lần lượt là $request - request từ phía người dùng và một closure tên là $next dùng để cho request của người dùng tiếp tục truy cập đến chức năng trong ứng dụng của bạn khi nó thỏa mãn hết các yêu cầu bạn đặt ra. Nếu bạn không biết closure là gì thì có thể đọc tại đây, còn không bạn có thể hiểu phần return $next($reuqest); sẽ cho phép request của người dùng được tiếp tục được thực hiện. Như ví dụ từ trước đó ta sẽ cần kiểm tra 2 thứ đó là người dùng đã đăng nhập và tài khoản có lớn hơn 50K VND. Ta có thể viết lại nội dung hàm handle() như sau:

public function handle($request, Closure $next)
{
    if (Auth::check() && Auth()::user()->getAccountBalance() > 50000) {
        return $next($request);
    }
    return redirect('home')->with('message', __('Some error message'));
}

Ở đoạn code trên thì nếu request từ phía người dùng thỏa mãn 2 điều kiện mà chúng ta đặt ra thì chúng ta sẽ cho họ tiếp tục truy cập vào tính năng còn trong trường hợp ngược lại thì chúng ta sẽ redirect người dùng quay về trang home kèm theo một đoạn message nào đó mà bạn mong muốn. Đến đây là chúng ta đã tạo ra được một Middleware để gom nhóm việc kiểm tra này lại vào trong một class rồi. Tiếp đến để dùng được nó thì ta sẽ cần khai báo nó trong file app/Http/Kernel.php. Bạn mở file này lên sẽ thấy có rất nhiều nội dung trong đó rồi nhưng chúng ta sẽ chưa bàn nó ở đây mà ta chỉ tập chung vào phần này:

protected $routeMiddleware = [
    '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,
];

Đây là nơi mà chúng ta sẽ khai báo các middleware để có thể dùng trong file routes/web.php của chúng ta, mặc định nó đã có rất nhiều các middleware khác mà Laravel đã tạo sẵn cho chúng ta để sử dụng nhưng ta sẽ không bàn đến ở đây. Để thêm middleware mà chúng ta mới tạo, bạn chỉ cần thêm nội dung sau vào ở dưới mạng nói trên:

'verfiy-account-balance' => App\Http\Middleware\VerifyAccountBalance::class,

Giờ đây thay vì bạn phải copy đi copy lại đoạn code kiểm tra 2 điều kiện mà bạn đặt ra trong tất các các hàm ở các controller thì bạn có thể đơn giản hóa nó ngay từ trong router như sau:

Route::group(['middleware' => 'verfiy-account-balance'], function() {
    Route::get('get-list-movie', '[email protected]');
    Route::get('watch-movie', '[email protected]');
    Route::get('download-movie', '[email protected]');
    // 47 chức năng khác
});

ở đây bạn nhớ đặt phần 'middleware' => 'verfiy-account-balance' trùng với tên mà chúng ta đã khai báo trong file Kernel.php. Giờ đây trước khi request từ người dùng được sử lý trong controller của chúng ta thì nó sẽ được kiểm tra theo điều kiện mà chúng ta đặt ra rồi mới có thể đi tiếp. Ngoài cách viết như trên thì để sử dụng middleware cho một route duy nhất thì ta cũng có thể viết:

Route::get('get-list-movie', '[email protected]')->middleware('verfiy-account-balance');

4. Sử dụng middleware đã có sẵn của Laravel


Ở trong file Kernel.php bạn sẽ thấy có một số phần như sau:

protected $middleware = [
    \App\Http\Middleware\TrustProxies::class,
    \App\Http\Middleware\CheckForMaintenanceMode::class,
    \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
    \App\Http\Middleware\TrimStrings::class,
    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];

Đây là các middleware dạng global nghĩa là tất cả các request từ phía người dùng sẽ được đi qua các class nói trên trước khi đi tiếp đến với chức năng của bạn

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        // \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    'api' => [
        'throttle:60,1',
        'bindings',
    ],
];

Đây là các middleware đã được gom nhóm lại và bạn có thể dụng nó để áp dụng cho các route của bạn. Mặc định thì toàn bộ các route bạn khai báo trong file web.php sẽ đều đi qua nhóm middleware web còn toàn bộ route trong file api.php sẽ đi qua nhóm middleware api

protected $routeMiddleware = [
    '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,
];

Còn đây là các middleware riêng lẻ mà bạn có thể sử dụng luôn ở trong code của mình. Cơ bản nhất là ở đây đã có sẵn middleware để kiểm tra việc người dung đã đăng nhập hay chưa là 'auth' => \App\Http\Middleware\Authenticate::class. Chính vì thế trong middleware VerifyAccountBalance ta sẽ bỏ bớt phần kiểm tra người dùng đã đăng nhập đi như sau:

public function handle($request, Closure $next)
{
    if (Auth()::user()->getAccountBalance() > 50000) {
        return $next($request);
    }
    return redirect('home')->with('message', __('Some error message'));
}

Lúc này trong file web.php phần khai báo route ta sẽ sửa lại một chút thành:

Route::group(['middleware' => ['auth', 'verfiy-account-balance']], function() {
    Route::get('get-list-movie', '[email protected]');
    Route::get('watch-movie', '[email protected]');
    Route::get('download-movie', '[email protected]');
    // 47 chức năng khác
});

Lưu ý ở đây bạn có thể sử dụng mảng để có thể dùng nhiều middleware cùng lúc và thứ tự chạy của các middleware sẽ theo thứ tự trong mảng.

5. Kết bài


Đến đây theo mình là đã có đủ các kiến thức cơ bản và hay dùng với middleware trong Laravel rồi nên mình xin phép dừng bài viết ở đây. Nếu bạn muốn bố sung điều gì hoặc không hiểu phần nào có thể comment ngay ở phía dưới. Cảm ơn các bạn đã đọc và đừng quên để lại một upvote nhé 😄