bài viết này mình xin giới thiệu về cách thức laravel chạy. để nếu bạn nào debug code laravel thì còn có sơ sở xem sai chỗ nào. Bài này không dành cho pro nhé

Cơ chế xử lý request trong Laravel

2021-01-04 1915 lượt xem
Cao Thị Ngọc

 😂😂😂 Mời các Pro cút khỏi bài viết để khỏi ném đá chủ thớt 😂😂😂

Javascript là 1 ngôn ngữ bị đồn là si-đa thì PHP lại là 1 ngôn ngữ không hề si-đa. Nó bị AIDS thôi. Đại loại là càng dễ tiếp cận càng dễ chơi cùng thì càng dễ si-đa. laravel không ngoại lệ, nó được viết trên nền php và dễ dùng, nhưng bạn đã thực sự hiểu laravel chạy như nào chưa? 

Laravel là một framework theo mô hình MVC (Model – View – Controller) và mọi request đều được phân tích từ 1 url của request. từ request đó chúng ta sẽ phân tách để biết được nên chạy controller nào, action nào. 

0 - tổng quan

Biến global của nodejs người dùng A lưu lại giá trị thì người dùng B cũng có thể thấy giá trị đó. còn biến global của PHP thì khác ( khác nodejs thôi chứ giống java nhé ).

khi người dùng gõ 1 url vào trình duyệt thì trình duyệt sẽ gửi tới cho server của bạn 1 request. Lúc này apache ( hoặc nginx ) sẽ tiếp nhận request của bạn và tạm hiểu là nó sẽ cấp phát bộ nhớ để tạo thread hoạt động độc lập 😁 

Mọi request của người dùng vào laravel quy cho cùng đều chạy vào public/index.php. Blog này mình đang viết code bằng laravel và bạn thử bấm link này để url có thêm chữ index.php nhưng vẫn chạy cùng 1 controller cùng action nha.

phân tích những thứ laravel làm

Auto Loader

khi laravel khởi tạo project sẽ download rất nhiều package vào trong vendor. và nếu muốn sử dụng các package thì php cần require vào. bạn vào public/index.php sẽ thấy đoạn code require sau: 

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

nếu bạn download 1 package khác từ  packagist.org thì bạn khỏi cần thêm autoload như các project khác. 

Load application

Sau khi load vendor thì ứng dụng laravel cần load 1 app trong folder bootstraps. bạn nhìn trong folder public/index.php thì bạn thấy đoạn require sau: 

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

thì trong file bootstrap/app.php mục đích chính là khởi tạo 1 cái app, sau đó binding 1 và thứ cần thiết vào app bằng singleton. ví dụ khởi tạo những thứ cần thiết như path, biến global, đăng ký service provider,...

bạn có thể xem kỹ hơn ở vendor\laravel\framework\src\Illuminate\Foundation\Application.php như sau: 

/**
     * Create a new Illuminate application instance.
     *
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }
        $this->registerBaseBindings(); /// tạo path và binding Service Container
        $this->registerBaseServiceProviders(); /// đăng ký  Service Providers cơ bản cho application
        $this->registerCoreContainerAliases(); /// nạp các alias
    }

Run application laravel

Nãy giờ không động chạm gì tới request, bây giờ mới là lúc chạy thật. 

Tạo Http Kernel

Để xử lý được request thì laravel khởi tạo kernel trước. dựa trên kernel sẽ xử lý request thông qua đối tượng request đã được nạp từ trên. Bạn tạm hiểu kernel là hạt nhân, core, lõi hay đại loại cái gì đó trung tâm xử lý cũng đc. Nó nhận vào request, đem đi phân tích và xử lý sau đó trả ra 1 response tương ứng. 

Xử lý request thông qua kernel

Quay lại đoạn này 

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

tuy ngắn gọn nhưng kinh khủng khiếp lắm các bạn.

Đầu tiên bạn mở app/Http/Kernel.php sẽ thấy nó extend từ HttpKernel ( tên định danh của class  Illuminate\Foundation\Http\Kernel ).

Lưu ý là laravel lấy nền từ Symfony nên không khó hiểu khi mà class Request extends SymfonyRequest

Dòng $request = Illuminate\Http\Request::capture() quy cho cùng là tạo ra cái biến $request để trong HttpKernel sẽ có hàm public function handle($request) sẽ handle biến $request. 

Trở lại app/Http/Kernel.php bạn xem thấy có 3 thuộc tính định nghĩa trong file này là: 

  • protected $middleware, là Global middleware, mảng middleware này được khai báo, và tất cả các request đến ứng dụng đều phải đi qua tưng cái 
  • protected $middlewareGroups, loại này để phân ra làm 2 loại web và api. ví dụ như web thì kệ mợ nó chứ api thì phải trả ra json, hay hạn chế đánh cắp dữ liệu thì phải sử dụng rating limit
  • protected $routeMiddleware.đây là middleware mà khi khai báo trong router thì mới chạy.

truyền request vào router

Khi laravel chạy function handle của Illuminate\Foundation\Http\Kernel thì sẽ có cái hàm cần chú ý là $this->sendRequestThroughRouter($request);

/**
     * Handle an incoming HTTP request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    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;
    }

Nếu bạn muốn tìm hiểu kỹ hơn, mình hy vọng bạn sẽ bắt đầu từ hàm này. Vì gần như mọi thứ lõi core đều được xử lý trong hàm này(Service Container, Request, File, Cookie, biến môi trường, các cấu hình, handle exception, đăng ký các Facades, đăng ký các Service Providers, và khởi động Service Provider, xử lý middleware, ...

Dispatch request

đối tượng request được tạo ra nhờ 1 loạt các biến global của PHP chạy vào Request của Symfony, đặc biệt biến $_SERVER để detect được url, bạn xem đoạn code này: 

/**
     * Creates a new request with values from PHP's super globals.
     *
     * @return static
     */
    public static function createFromGlobals()
    {
        $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);

        if ($_POST) {
            $request->request = new InputBag($_POST);
        } elseif (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
            && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
        ) {
            parse_str($request->getContent(), $data);
            $request->request = new InputBag($data);
        }

        return $request;
    }

Cụ thể, hàm sendRequestThroughRouter ở phần truyền request vào router sẽ start các hàm con như dispatchToRoutefindRoute cuối cùng là hàm getRequestUri trong Symfony\Component\HttpFoundation\Request.

Vì khi request được khởi tạo có truyền nguyên biến $_SERVER vào nên trong hàm prepareRequestUri có thể lấy REQUEST_URI trong biến $_SERVER ra để tìm đúng router.

Response được trả về và đóng thread.

Sau khi có $response nó sẽ được trả về cho người dùng từ mục xử lý request thông qua kernel thì cũng đã đến lúc gửi về cho client và đóng ứng dụng. 

$response->send(); // Gửi response đến người dùng
$kernel->terminate($request, $response); // Đóng Kernel và kết thúc vòng đời của một request

Tổng kết

dài quá nên mình tổng kết quá trình tổng thể của laravel sinh ra 1 response là: 

  • Auto loader: Tải những gì cần thiết (packages, library, classmap…) đầu tiên.
  • Khởi tạo Laravel Application.
  • Đăng ký và tạo Http Kernel, Console Kernel, Exceptions Handle.
  • Đăng ký các middleware trong Illuminate\Foundation\Http\Kernel.
  • Đăng ký request hiện tại thay thế cho request mới nhất ứng dụng nhận được để xử lý.
  • Load bootstrappers: load biến môi trường, cấu hình, …
  • Đăng ký và xử lý Service Providers
  • Chuyển request qua các Middleware hoặc route và xử lý chúng theo ý dev ( có đôi khi bạn sẽ chạy affter middleware nên tùy lúc middleware sẽ chạy trước) và khi xử lý route, một chuỗi các hoạt động từ Router đến Controller đến Model và trả về một Response.
  • Gửi response về cho người dùng.
  • Kết thúc vòng đời của một request bằng việc đóng lại Http Kernel.
những tag
bài viết trong chủ đề