Cơ cấu xử lý request trong Laravel như thế nào?

1. Giới thiệu

Laravel là một framework theo mô hình MVC (Model – View – Controller). Trong mô hình này, một request sẽ chạy đến Router. Sau đó nó tiếp tục đến Controller(C), Controller sẽ có thể tiếp tục xử lý tại Model (M) và trả dữ liệu ra View (V).

Trong Laravel framework, một request về cơ bản thì sẽ chạy theo cách nói ở trên. Tuy nhiên, bên dưới của nó thực sự có những gì đang diển ra.

2. Nội dung

Khi một request được bắt đầu từ Router thì để có được một response, nó phải trải qua những gì. Mọi thứ sẽ được bắt đầu trong app từ public/index.php.

2.1. Auto Loader

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

Đầu tiên nó sẽ gọi đến file autoload.php. Nó sẽ tự động load tất cả các package/library được đăng ký bởi Composer trong quá trình sử dụng.

Tham khảo thêm các vị trí tuyển dụng Laravel lương cao hấp dẫn

2.2. Khởi tạo một instance từ Laravel App

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

Line đầu tiên:

$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

Bên trong có gì:

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

    $this->registerBaseBindings();

    $this->registerBaseServiceProviders();

    $this->registerCoreContainerAliases();
}

Trong __constructor nó nhận vào một cái $basePath = null. Sau đó nó sẽ kiểm tra sự tồn tại của $basePath này và sẽ tạo ra các global path cho các thư mục như base, lang, config, public, storage, database, resources, bootstrap mà chúng ta vẫn sử dụng.
Ví dụ:

$this->instance('path.config', $this->configPath());

public function configPath($path = '')
{
    return $this->basePath.DIRECTORY_SEPARATOR.'config'.($path ? DIRECTORY_SEPARATOR.$path : $path); // define ('DIRECTORY_SEPARATOR', "/");
    
}

Vậy tại sao nói nó tạo ra các global đường dẫn ??? Đã bao giờ các bạn sử dụng các hàm như config_pathpublic_path, ... chưa.
Tất cả các hàm này đều tạo ra một instance với cái tên 'path.name', trùng với các thư mục trong application của bạn. Và chúng được tạo ra bởi các hàm trong file helpers.php.

if (! function_exists('config_path')) {
    /**
     * Get the configuration path.
     *
     * @param  string  $path
     * @return string
     */
    function config_path($path = '')
    {
        return app()->make('path.config').($path ? DIRECTORY_SEPARATOR.$path : $path);
    }
}

Khi bạn gọi config_path(), mặc định Laravel sẽ tạo ra một instance của cái tên path.config. Mà hàm này chính là đường dẫn đến file folder config đã nói ở trên.
Hàm $this->registerBaseBindings(); sẽ tạo một instance của 'app' và một instance của class Container::class bằng chính cái class Illuminate\Foundation\Application.php này.

$this->instance('app', $this);

$this->instance(Container::class, $this);

Mở cái class Container.php kia ra mình có thấy rất nhiều các methods quen thuộc liên quan đến khái niệm về Service Container như bindsingleton.

Có thể bạn quan tâm: 

  Nhận diện khuôn mặt trong ứng dụng Laravel sử dụng Google Cloud Vision API

Và cuối cùng là khởi tạo một instance của PackageManifest.
Tiếp theo:

$this->registerBaseServiceProviders();

Laravel sẽ đăng kí các Service Provider cơ bản cho application. Tưởng tượng như project của chúng ta sẽ luôn luôn cần có những cái cơ bản, cái gốc để nó có thể hoạt động được. Chúng ta có thể thấy Laravel đăng ký các provider như Event, Log, Routing:

$this->register(new EventServiceProvider($this));

$this->register(new LogServiceProvider($this));

$this->register(new RoutingServiceProvider($this));

Cuối cùng trong hàm __constructor là:

$this->registerCoreContainerAliases();
public function registerCoreContainerAliases()
{
    foreach ([
        'app'                  => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class],
        'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
       // ...
    ] as $key => $aliases) {
        foreach ($aliases as $alias) {
            $this->alias($key, $alias);
        }
    }
}

Hàm này có tác dụng đăng ký các tên cho các class trong Laravel Application. Ví dụ như 'app' sẽ được gán cho \Illuminate\Foundation\Application::class\Illuminate\Contracts\Container\Container::class\Psr\Container\ContainerInterface::class].
Ví dụ khi bạn gọi app()->make('hash') thì 'hash' chính là \Illuminate\Hashing\HashManager::class.
Bước tiếp theo, Laravel sẽ đăng kí binding cho class Http\Kernel và Console\Kernelcùng với đó là một class Exception, mục tiêu là để phục vụ cho việc handle Request và handle các lỗi khi xảy ra từ Http và giao diện Console. (Terminal).
Sau khi binding xong , $app này sẽ được trả về.

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;

2.3. Tạo Http Kernel

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

Ở trên chúng ta thấy Laravel binding class Illuminate\Contracts\Http\Kernel::classbằng một App\Http\Kernel::class. Vậy hàm này sẽ trả về cho chúng ta một instance của Illuminate\Contracts\Http\Kernel::class, chính là App\Http\Kernel.php

2.4. Handle Request

Http Kernel này sẽ xử lý $request và $response là kết quả của quá trình xử lý:

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

Giờ chúng ta cùng tìm hiểu xem Laravel của chúng ta thực sự đã làm những gì để xử lý một request nhé. Chúng ta hãy mở class App\Http\Kernel ra. Trong này có rất nhiều groupMiddleware khác nhau. Nếu để ý bạn có thể thấy có một nhóm globalMiddleware, một middlewareGroup và một routeMiddleware, nhiệm vụ của chúng là dùng để lọc các request chạy đến application của bạn, sau đó xử lý như thế nào tiếp theo sẽ là do phần xử lý bên trong của middleware đó.
Nhóm globalMiddleware là phần mà mỗi request đều phải đi qua nó. Ví dụ như middleware \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class

protected function transform($key, $value)
{
    return is_string($value) && $value === '' ? null : $value;
}

Hàm này chắc các bạn cũng hiểu rồi phải không.
Bạn mở class mà Kernel kế thừa ra tại Illuminate\Foundation\Http\Kernel.
Kéo xuống phần hàm handle($request) ta thấy có đoạn

$response = $this->sendRequestThroughRouter($request);

Mở hàm sendRequestThroughRouter($request) và 2 hàm dưới luôn nhé.

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request); // Đăng ký request hiện tại với Laravel Service Container với tên `'request'`

    Facade::clearResolvedInstance('request'); // Loại bỏ request facade khác

    $this->bootstrap(); // Load các bootstrapper

    return (new Pipeline($this->app)) //Đẩy request qua các middleware và cuối cùng là đẩy request lên router
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

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

/**
 * Get the route dispatcher callback.
 *
 * @return \Closure
 */
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);

        return $this->router->dispatch($request);
    };
}

Đến đây, gần như quá trình handle một request đã xong. Quay lại hàm handle($request) tại Illuminate\Foundation\Http\Kernel.php, sau khi dispatch request lên router và đã được xử lý, nó sẽ dispatch một event bao để báo rằng request đã được xử lý, hàm này gồm 2 tham số quen thuộc là $request và $response.

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

return $response // cuối cùng là phải return cái $response này

2.5. Kết thúc

Sau khi có $response nó sẽ được trả về cho người dùng

$response->send();

Quá trình handle một request được kết thúc, cuối cùng là đóng Kernel và kết thúc.

$kernel->terminate($request, $response);

3. Tổng kết

Vòng đời của một request trong Laravel.

  • Auto Loader.
  • Khởi tạo ứng dụng, đăng ký các global_path, provider cơ bản, các bí danh cho các class.
  • Binding class Kernel, Exception.
  • Tạo Kernel.
  • Đăng ký Service Provider
  • Send request qua middleware => send request tới router
  • Gửi response về cho người dùng
  • Kết thúc

Và thành phần quan trọng nhất trong quá trình handle một request trong Laravel chính là Service Provider.

Tham khảo thêm việc làm laravel không yêu cầu kinh nghiệm cao tại đây

  Tìm hiểu SQL Transaction và cách sử dụng trong Laravel