Vòng đời request Laravel

Mỗi khi bạn hiểu về cách thức hoạt động của 1 framework thì bạn có thể tự tin sử dụng nó một cách hiệu quả nhất .Vì vậy , bài viết này sẽ giúp bạn hiểu rõ được vòng đời request của Laravel .

1.Mô hình vòng đời request Laravel

Bắt đầu từ việc Client gửi 1 request lên server . Vì Laravel đặt root document trỏ vào thư mục public ,root document trong apache gọi là DocumentRoot, trong nginx gọi là root . Nên mọi request từ client đều phải đi qua file public/index.php. Như vậy , file đầu tiên được khởi tạo đó chính là public/index.php như hình trên . Ta sẽ tìm hiểu về file public/index.php

2.Public/index.php

2.1 Kiểm tra ứng dụng có được bảo trì hay không .


if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) 
{
    require __DIR__.'/../storage/framework/maintenance.php';
}

Nếu ứng dụng đang ở chế độ bảo trì , Laravel sẽ load file storage/framework/maintenance.php .Các bạn có thể chạy kiểm tra bằng cách sử dụng câu lệnh : php artisan down

2.2 Đăng ký cơ chế autoload

    
require __DIR__.'/../vendor/autoload.php';
    

Các thư viện trong Laravel được composer install và lưu trong thư mục vendor . autoload.phpcó chức năng include/require các thư viện này vào  .

2.3 Binding Kernel cho Laravel Framework


$app = require_once __DIR__.'/../bootstrap/app.php'

File bootstrap/app.php trả về 1 instance applicaton .Chúng ta hãy tìm hiểu logic file bootstrap/app.php

2.3.1 Tạo instance application

 
$app = new Illuminate\Foundation\Application(
        dirname(__DIR__)
 );
 

Illuminate\Foundation\Application kế thừa class container .Nên nó mang hình bóng của 1 service container có các phương thức : bind , singleton , instance , make,..để phục vụ việc bind và resolve các class , interface khi cần . Ta cũng có thể nói rằng ,instance application là 1 service container .

Ta sẽ đi sâu vào Illuminate\Foundation\Application :

public function __construct($basePath = null)
{
    if ($basePath) {
        $this->setBasePath($basePath);
    }

    $this->registerBaseBindings();

    $this->registerBaseServiceProviders();

    $this->registerCoreContainerAliases();
}
  • $basePath :Tạo ra các global path cho các thư mục như base, lang, config, public, storage, database, resources, bootstrap
  • $this->registerBaseBidings() :Khởi tạo container để chứa các dependencies trong ứng dụng
  • $this->registerBaseServiceProviders():Đăng kí 1 sốService Provider cơ bản cho aplication:

$this->register(new EventServiceProvider($this));     //Quản lý sự kiện
$this->register(new LogServiceProvider($this));       //Quản lý log
$this->register(new RoutingServiceProvider($this));   //Quản lý định tuyến

  • $this->registerCoreContainerAliases: đăng ký các bí danh cho Core Container .Các bạn có thể tìm thấy trong config/app ở phần 'aliases'

2.3.2 Bind các class ,interface vào instance application

Bạn đã bao giờ đặt câu hỏi rằng vì sao Laravel không bao giờ phải khởi tạo object mà chương trình vẫn chạy mượt mà không ?.Đó là Laravel đã sử dụng Inversion of Control , hay còn gọi là Dependency Injection Container . Có nghĩa là inject(tiêm) tất cả các class vào Container , bao giờ muốn khởi tạo thì lấy nó ra .  Để hiểu hơn , bạn nên tìm hiểu Service Container trong Laravel trước khi đọc bài này. Đọc xong bài này thì bạn nên đọc bài Service Providers . Đó là combo dành cho các bạn tò mò ,thích tìm hiểu.

Laravel tiến hành binding các  Interface Kernel và implement của interface Kernel đó là các class sẽ được sinh ra khi resolve nó .Thay vì ta inject cụ thể 1 instance , ta sẽ inject 1 interface từ đó  ta có thể linh hoạt inject instance khác nhau ,miễn là nó implement interface đó .

//Xử lý các request Http 
$app->singleton(
        Illuminate\Contracts\Http\Kernel::class,
        App\Http\Kernel::class
);
//Xử lý các request từ Console , vd các lệnh php artisan 
$app->singleton(
        Illuminate\Contracts\Console\Kernel::class,
        App\Console\Kernel::class
);
// debug Exception Handler
$app->singleton(
        Illuminate\Contracts\Debug\ExceptionHandler::class,
        App\Exceptions\Handler::class
);

return $app;

2.4 Make Kernel

Sau khi đã binding các interface kernel sẽ tiến hành resolve các interface này


$kernel = $app->make(Kernel::class)

Khi bạn truy cập:
+từ trình duyệt web (http): sẽ tạo ra instance của interface Illuminate\Contracts\Http\Kernel chính là class App\Http\Kernel.php
+từ dòng lệnh console: sẽ tạo ra instance của interface Illuminate\Contracts\Http\Kernel  chính là class App\Console\Kernel.php

2.4.1 Phân tích Http Kernel

App\Http\Kernel.php kế thừa Illuminate\Foundation\Http\Kernel.php . Kernel tiến hành nạp thông tin middleware và các bootstrapper :

-Global Middleware :

+CheckForMaintenanceMode: Kiểm tra app đang ở trạng thái bảo trì?
+ValidatePostSize: Kiểm tra post size (giới hạn kích thước post lên server )
+TrimStrings: Toàn bộ request string sẽ được strim hết.
+ConvertEmptyStringsToNull: Chuyển string thành null hết, kết hợp với TrimStrings là cặp bài trùng.
+TrustProxies: dùng cho load balance, liệt danh sách proxy tin tưởng.

-Middleware Group :

+EncryptCookies: mã hóa cookie
+AddQueuedCookiesToResponse: thêm cookie vào response
+StartSession: khởi động session
+ShareErrorsFromSession: chia sẻ lỗi từ session
+VerifyCsrfToken: check csrf token (một lỗi bảo mật)
+SubstituteBindings: route sử dụng Eloquent model bindings, sẽ tự động thay thế paramete thành Eloquent model.

-Bootstrappers:
+LoadEnvironmentVariables: kiểm tra và load ra thông tin file môi trường (.env),
+LoadConfiguration: load toàn bộ config (cache hoặc đọc file)
+HandleExceptions: cấu hình xử lý lỗi
+RegisterFacades: đăng ký các facades cấu hình trong app.php
+RegisterProviders: đăng ký các providers cấu hình trong app.php (quan trọng)
+BootProviders: chạy Application

-Middleware Route : Cái này các bạn tự tìm hiểu

Lưu ý là toàn bộ bootstrapers và middleware lúc này chưa được khởi tạo .

2.5 Tạo Service Providers

Từ nãy tới giờ chưa nhắc tới request , request người dùng gửi lên sẽ bắt đầu vào được xử lý từ đoạn này.

$response = tap($kernel->handle(
    $request = Request::capture() //Nhận tất cả các thông tin từ request
))->send();

Chúng ta cùng xem logic xử lý của method handle trong

Illuminate\Foundation\Http\Kernel.php :


public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride(); 

            $response = $this->sendRequestThroughRouter($request);
        } catch (Throwable $e) {
            $this->reportException($e);

            $response = $this->renderException($request, $e);
        }

        $this->app['events']->dispatch(
            new RequestHandled($request, $response)
        );

        return $response;
    }

Chúng ta quan tâm tới $this->sendRequestThroughRouter ($request).

protected function sendRequestThroughRouter($request)
        {
            //Đăng ký request với instance application 
            $this->app->instance('request', $request); 
            
           //Loại bỏ request facade khác
            Facade::clearResolvedInstance('request');

            // load các bootstappers
            $this->bootstrap();

            return (new Pipeline($this->app))
            ->send($request)
            ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
            ->then($this->dispatchToRouter());
        }

$this->bootstrap() : Kiểm tra mảng $bootstrappers có rỗng hay ko , nếu ko rỗng sẽ gọi tới $app->bootstrapWith()

public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
}

bootstrapWith(): Tiến hành tạo ra các bootstrapper trong mảng $bootstrappers:

public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);

            $this->make($bootstrapper)->bootstrap($this);

            $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
        }
    }

Trong các bootstrapper được tạo , ta quan tâm tới RegisterProviders .RegisterProviders như các bootstrapper , sẽ chạy hàm bootstrap($this) .Bootstrap($this) lại sử dụng tới method registerConfiguredProviders() của Illuminate\Foundation\Application

// Illuminate\Foundation\Application

public function registerConfiguredProviders()
    {
        $providers = Collection::make($this->make('config')->get('app.providers'))
                        ->partition(function ($provider) {
                            return strpos($provider, 'Illuminate\\') === 0;
                        });

        $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

        (new ProviderRepository($this, new Filesystem, 
        $this->getCachedServicesPath()))
                    ->load($providers->collapse()->toArray());
    }

Đây là câu trả lời cho việc các servide providers được tạo ra như thế nào trong ứng dụng . Vì vậy , mỗi khi tạo service providers , ta phải đăng kí trong mảng providers ở  config/app.

Service providers là trung tâm khởi tạo của ứng dụng có nhiệm vụ khởi tạo ra tất cả các thành phần của ứng dụng , ví dụ database , validation , routing,.. . Chúng ta có thể xem các service providers này trong config/app ở mảng providers .

Các service providers sẽ chứa method register . Đầu tiên register() sẽ được gọi ở tất cả các providers, rồi sau đó, khi mà các providers đã được đăng kí đầy đủ, thì hàm boot sẽ được gọi.

Service providers thực sự là chìa khoá để khởi tạo Laravel. Khi đối tượng của ứng dụng được tạo ra, các service providers đã được đăng kí, thì các request sẽ được đẩy tới phần đã được khởi tạo của ứng dụng. Đơn giản chỉ vậy thôi!

2.6 Điều hướng qua Router

Chúng ta quay lại SendRequestThroughRouter($request) .Sau khi tạo ra service providers ,hàm này sẽ trả về :


return (new Pipeline($this->app))->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());

Đây là lí do vì sao các request sẽ luôn đi qua middleware global được khai báo trong mảng $middleware trong Http/Kernel.

Sau khi đi qua Middleware global ,request sẽ tiếp tục được đi qua router.

Tại route , ta cài đặt các middleware cho mỗi route .Các middleware route được khai báo trong mảng $routeMiddleware trong Http/Kernel  . Sau khi $request vượt qua middleware route , $request sẽ được điều hướng tới controller hoặc tới view .Điều này chúng ta đã quá quen thuộc với chúng ta .

3. Middleware

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 .

-Middleware Global : Được khai báo trong biến $middleware . Tất cả các request đều phải đi qua middleware Global

-Middleware Group : Là 1 nhóm middleware ,được sử dụng với các request từ web , api

-Middleware Route : Các request được đi qua khi bạn cài đặt nó trong Route .

Ngoài các request phải đi qua middleware thì các response trả về cũng sẽ được đi qua middleware :

Để làm được điều này , ta cần tạo 1 hàm  terminable vào trong các middleware .Hàm này nhận đầu vào cả $request, cả $response .Sau đó bạn sẽ phải đăng kí nó trong middleware global .

Hy vọng bài viết này sẽ giúp bạn hiểu được chi tiết được Laravel hoạt động như thế nào !.