124.1 underlying operating principle of laravel framework

Posted by EsOne on Mon, 20 Dec 2021 18:35:14 +0100

Overview of operation principle

The entry file index. Of the Laravel framework php
1. Introduce autoload PHP file
2. Create an application instance and complete it at the same time

Basic binding($this,Container class Container wait),

Registration of basic service providers( Event,log,routing),

Registration of core class aliases(such as db,auth,config,router etc.)

3. Start processing of Http request

make Method resolves the specified value from the container to the actual class, such as $app->make(Illuminate\Contracts\Http\Kernel::class); Analyze it App\Http\Kernel 

handle Method pair http Request processing

Actually handle in sendRequestThroughRouter handle http Request for

First, put request Bind to shared instance

Then execute bootstarp Method to run the given array of boot classes $bootstrappers,This is the focus, including loading configuration files, environment variables, service providers, facade, exception handling, boot providers, etc

Then, enter the pipeline mode, process and filter through the middleware, and then distribute the user request

When distributing a request, first, find the route matching the given request, and then execute runRoute Method, when actually processing the request runRoute Medium runRouteWithinStack 

Finally, after runRouteWithinStack Medium run Method, assign the request to the actual controller, execute the closure or method, and get the response result

4. Return processing results

Detailed source code analysis

1. Register the automatic loading class to realize the automatic loading of files

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

2. Create an Application Container instance (the instance inherits from the Container class Container) and bind the core (web, command line, exception) to resolve them when needed

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

app. The PHP file is as follows:

<?php
// Create Laravel instance [3]
$app = new Illuminate\Foundation\Application(
 $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
// Binding Web side kernel
$app->singleton(
 Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class);
// Bind command line kernel
$app->singleton(
 Illuminate\Contracts\Console\Kernel::class, App\Console\Kernel::class);
// Binding exception handling
$app->singleton(
 Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\Handler::class);
// Return application instance
return $app;

3. In the constructor for creating an application instance (Application.php), register the basic binding to the container, register all basic service providers, and register the core class alias in the container

3.1. Register the basic binding in the container

    /**
     * Register the basic bindings into the container.
     *
     * @return void
     */
    protected function registerBaseBindings()
    {
        static::setInstance($this);
        $this->instance('app', $this);
        $this->instance(Container::class, $this);
        $this->singleton(Mix::class);
        $this->instance(PackageManifest::class, new PackageManifest(
            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
        ));
        # Note: the instance method is to Register as a shared instance, and the singleton method is to Register as shared binding
    }

3.2. Register all basic service providers (events, logs, routes)

protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));
        $this->register(new LogServiceProvider($this));
        $this->register(new RoutingServiceProvider($this));
    }

3.3. Register the core class alias in the container

4. The above completes the automatic loading of classes, the registration of service providers, the binding of core classes, and the binding of basic registration

5. Start parsing http request

index.php
//5.1
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
//5.2
$response = $kernel->handle(
 $request = Illuminate\Http\Request::capture());

5.1. The make method parses the given value from the container

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

Medium Illuminate\Contracts\Http\Kernel::class Yes index.php Medium $app = require_once __DIR__.'/../bootstrap/app.php';The binding actually points to App\Http\Kernel::class This class

5.2. http requests are processed here

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

Enter the class app \ http \ kernel represented by $kernel In PHP, we can see that there are only some middleware related contents defined, and there is no handle method

Let's go to its parent class use Illuminate\Foundation\Http\Kernel as HttpKernel; Find the handle method in, and you can see that the handle method is like this

public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();
        // The core place to handle http requests [6]
        $response = $this->sendRequestThroughRouter($request);
    } catch (Exception $e) {
        $this->reportException($e);
        $response = $this->renderException($request, $e);
    } catch (Throwable $e) {
        $this->reportException($e = new FatalThrowableError($e));
        $response = $this->renderException($request, $e);
    }
    $this->app['events']->dispatch(
        new Events\RequestHandled($request, $response)
    );
    return $response;
}

6. Process Http requests (bind request to shared instance and process user requests using pipeline mode)

vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php of handle method
// The core place to handle http requests
$response = $this->sendRequestThroughRouter($request);

protected function sendRequestThroughRouter($request)
{
    // Bind request $request to shared instance
    $this->app->instance('request', $request);
    // Clear the request request from the resolved facade instance (because it has been bound to the shared instance, there is no need to waste resources)
    Facade::clearResolvedInstance('request');
    // Bootstrap the application for HTTP requests
    $this->bootstrap();[7,8]
    // Enter the pipeline mode, go through the middleware, and then process the user's request [9, 10]
    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

7. In the bootstrap method, run the given boot class array $bootstrappers, load the configuration file, environment variables, service provider, facade, exception handling and boot provider. The very important step is located in vendor / laravel / framework / SRC / illuminate / foundation / HTTP / kernel php

/**
 * Bootstrap the application for HTTP requests.
 *
 * @return void
 */
public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());
    }
}

8. Load config / APP Services defined in the providers array in PHP

Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
......

/**
 * Add your own service provider */\App\Providers\HelperServiceProvider::class,

You can see that common Redis, session, queue, auth, database, Route and other services are loaded here

9. The pipeline mode is used to process user requests, which are processed and filtered through the middleware first

return (new Pipeline($this->app))
    ->send($request)
    // If the middleware is not disabled for the program, load the middleware (located in the $middleware attribute of app/Http/Kernel.php)
    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
    ->then($this->dispatchToRouter());
}

app/Http/Kernel.php

/**
 * Global HTTP middleware for applications
 *
 * These middleware are run during every request to your application.
 *
 * @var array
 */
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,
];

10. After being processed by the middleware, the request is distributed (including finding the matching route)

/**
 * 10.1 Send the given request through Middleware / router
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
 protected function sendRequestThroughRouter($request)
{
    ...
    return (new Pipeline($this->app))
        ...
        // Request distribution
        ->then($this->dispatchToRouter());
}
/**
 * 10.2 Get route scheduler callback
 *
 * @return \Closure
 */
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);
        // Send request to application
        return $this->router->dispatch($request);
    };
}
/**
 * 10.3 Send request to application
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
 */
 public function dispatch(Request $request)
{
    $this->currentRequest = $request;
    return $this->dispatchToRoute($request);
}
 /**
 * 10.4 Dispatch the request to the route and return the response [focus on the runRoute method]
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
 */
public function dispatchToRoute(Request $request)
{   
    return $this->runRoute($request, $this->findRoute($request));
}
/**
 * 10.5 Find a route that matches the given request
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Routing\Route
 */
protected function findRoute($request)
{
    $this->current = $route = $this->routes->match($request);
    $this->container->instance(Route::class, $route);
    return $route;
}
/**
 * 10.6 Find the first route that matches the given request
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Routing\Route
 *
 * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
 */
public function match(Request $request)
{
    // Get the user's request type (get, post, delete, put), and then select the corresponding route according to the request type
    $routes = $this->get($request->getMethod());
    // Matching route
    $route = $this->matchAgainstRoutes($routes, $request);
    if (! is_null($route)) {
        return $route->bind($request);
    }
    $others = $this->checkForAlternateVerbs($request);
    if (count($others) > 0) {
        return $this->getRouteForMethods($request, $others);
    }
    throw new NotFoundHttpException;
}

By now, the route matching the request has been found and will be run later, that is, the runRoute method in 10.4

/**
 * 10.7 Returns the response for a given route
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Illuminate\Routing\Route  $route
 * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
 */
protected function runRoute(Request $request, Route $route)
{
    $request->setRouteResolver(function () use ($route) {
        return $route;
    });
    $this->events->dispatch(new Events\RouteMatched($route, $request));
    return $this->prepareResponse($request,
        $this->runRouteWithinStack($route, $request)
    );
}
/**
 * Run the given route within a Stack "onion" instance.
 * 10.8 To run the route in the stack, first check whether there is a controller middleware. If so, run the controller middleware first
 *
 * @param  \Illuminate\Routing\Route  $route
 * @param  \Illuminate\Http\Request  $request
 * @return mixed
 */
protected function runRouteWithinStack(Route $route, Request $request)
{
    $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                            $this->container->make('middleware.disable') === true;
    $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
    return (new Pipeline($this->container))
        ->send($request)
        ->through($middleware)
        ->then(function ($request) use ($route) {
            return $this->prepareResponse(
                $request, $route->run()
            );
        });
}
 /**
     * Run the route action and return the response.
     * 10.9 The last step is to run the controller to process the data
     * @return mixed
     */
    public function run()
    {
        $this->container = $this->container ?: new Container;

        try {
            if ($this->isControllerAction()) {
                return $this->runController();
            }

            return $this->runCallable();
        } catch (HttpResponseException $e) {
            return $e->getResponse();
        }
    }

11. Run the route and return the response (key points). You can see that one method in 10.7 is prepareResponse, which creates a response instance from the given value, while the runRouteWithinStack method runs the route in the stack, that is, http requests and responses will be completed here.