Laravel解析 $request $kernel $response -入口应用初始化

laravel是单一入口模式所有请求从public/index.php进入

define(‘LARAVEL_START’, microtime(true));
require __DIR__.’/../vendor/autoload.php’;
$app = require_once __DIR__.’/../bootstrap/app.php’;
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);

  我们都知道 web程序做的事情就是

  1.用户从浏览器发送请求请求

  2.程序接收请求进行计算网页程序

  3.运算结果返回给浏览器网页响应

  index文件中的$request $kernel $response 就是对应着请求、计算、响应

  请求部分使用syfmony的request组件对刘篮球发出的请求头信息进行打包收集形成一个对象来方便我们操作

  $kernel算是laravel的请求处理核心了通过request里的url找到相应路由的控制器

  执行后返回视图等响应并将$response输出至浏览器。

步骤解析
第一步define(‘LARAVEL_START’, microtime(true));
  首先计算启动框架的时间但是在整个生命周期里面却没有用到过

第二步require __DIR__.’/../vendor/autoload.php’;
  这行代码引入了composer启动文件PHP所需要的文件都需要在这里加载laravel有一个composer.json文件写入了我们需要的依赖composer启动的时候会把这些依赖缓存成key/value数组出发spl_autoload函数进行加载

第三步$app = require_once __DIR__.’/../bootstrap/app.php’;
  这行数据实例化了一个$app的应用程序对象这是个关键让我们看一下这个文件

‹?php
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.’/../’)
);
$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;

  它做的事情不多 显示实例化了一个application对象然后把http和控制台kernel还有异常处理实例绑定到了这个对象中返回给了index文件

  我们来解析一下实例化application对象的过程

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

  构造函数做了下面的四件事

第一件
  加载了项目的一些路径存储到了$app对象里面我们查看setBasePath方法

public function setBasePath($basePath)
    {
        $this->basePath = rtrim($basePath, ‘\/’);
        $this->bindPathsInContainer();
        return $this;
    }

  在查看bindPathsInContainer方法

protected function bindPathsInContainer()
    {
        $this->instance(‘path’, $this->path());
        $this->instance(‘path.base’, $this->basePath());
        $this->instance(‘path.lang’, $this->langPath());
        $this->instance(‘path.config’, $this->configPath());
        $this->instance(‘path.public’, $this->publicPath());
        $this->instance(‘path.storage’, $this->storagePath());
        $this->instance(‘path.database’, $this->databasePath());
        $this->instance(‘path.resources’, $this->resourcePath());
        $this->instance(‘path.bootstrap’, $this->bootstrapPath());
    }

  具体的实现代码在其父类Container类的instance方法中代码很简单就一句是$this->instances[$abstract] = $instance;。

public function instance($abstract, $instance)
    {
        $this->removeAbstractAlias($abstract);
        $isBound = $this->bound($abstract);
        unset($this->aliases[$abstract]);
        // We’ll check to determine if this type has been bound before, and if it has
        // we will fire the rebound callbacks registered with the container and it
        // can be updated with consuming classes that have gotten resolved here.
        $this->instances[$abstract] = $instance;
        if ($isBound) {
            $this->rebound($abstract);
        }
        return $instance;
    }

  我们发现绑定了许多系统路径在这里我们打印一下$this看一下

第二件
  registerBaseBindings 这个方法和上面那个差不多就是把当前的$this对象绑定到了$this里面的instancekey叫做app和container 并且把bootstrap/cache/packages.php里面的prproviders服务提供者路径传入了PackageManifest类中并绑定到了$this也就是app对象实例中的instance中去打印这时候的$this会看到

第三件
  registerBaseServiceProviders方法

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

  注册了基本的providers event、log、router的服务提供者打印此时的app对象

  你户发现 serviceProviders里面和bindings里面都有了对应的值bindings属性也增加了provide对应的boot闭包闭包中存储的是实例化对象的代码允许后会得到一个对象实例以闭包的形式存储下来进行按需加载

第四件
  registerCoreContainerAliases方法跟他的名字一样加载了容器的核心类别名打印出此时的app对象会发现aliases、abstractAliases里面都多了很多相应的映射数组方便以后实例化对象

总结
  application类出事化工作大概概括为这四件事1、设置路径 2、绑定了app对象和packages包的实力 3、注册了基本的服务提供者 4、绑定了核心类的别名 全都是一些配置工作

这时候回到了app文件里面
  $app进行了三个简单绑定绑定http和控制台的kernel还有异常处理的实例到$app里面我们一路追踪追踪到

  \vendor\laravel\framework\src\Illuminate\Container\Container.php文件的bind方法中有很长的代码其实前面都在进行状态判断这个函数所做的事情韩式对于传入的类名路径转会为一个启动服务的闭包保存到$app的属性bindings里面getClosure方法的代码也可以看一下比较简单

public function bind($abstract, $concrete = null, $shared = false)
    {
        //抽象类型判断
        $this->dropStaleInstances($abstract);
        if (is_null($concrete)) {
            $concrete = $abstract;
        }
        //这一阶段重点刚刚我们index传入的类路径不是闭包就会在这里被getClosure方法转换成一个返回对象实例的闭包了
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }
        //将闭包绑定在bindings属性中
        $this->bindings[$abstract] = compact(‘concrete’, ‘shared’);
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }
protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            if ($abstract == $concrete) {
                return $container->build($concrete);
            }
            return $container->make($concrete, $parameters);
        };
    }

  这是我们打印$app会看到 bindings里面多了几个相应的属性见下图http/kernel用来处理http请求console/kernel用来处理artisan命令debug/exceptionHandler用来处理异常错误

这时候app.php文件做完了事情返回给index文件一个app实例
  我们回到index文件第四步index文件就立马利用$app对象来make了一个kernel实例make就是制造我们看一下vendor\laravel\framework\src\Illuminate\Foundation\Application.php类的make方法

public function make($abstract, array $parameters = [])
    {
        //这里获取了传入类的别名getAlias方法通过递归取出存储在容器中的别名不过现在kernel没有别名所以还是刚刚传入的类路径
        $abstract = $this->getAlias($abstract);
        //也不是延迟加载服务直接跳转到父类make方法
        if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) {
            $this->loadDeferredProvider($abstract);
        }
        return parent::make($abstract, $parameters);
    }

  然后到了他的父类的make方法

public function make($abstract, array $parameters = [])
    {
        return $this->resolve($abstract, $parameters);
    }

  继续追踪resolve方法下面是代码

protected function resolve($abstract, $parameters = [])
    {
        $abstract = $this->getAlias($abstract);
        //是否存在构建上下文此出为了服务提供者的契约
        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );
        //instances数组中有该类并且不需要构建上下文的话便直接返回该类实例
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }
        //将实例化类所需的参数存入数组
        $this->with[] = $parameters;
        //获取该类闭包若无则还是返回类名字符串
        $concrete = $this->getConcrete($abstract);
        //若当前所make的类没有上下文绑定并且是一个闭包则直接进行构建否则再次递归make方法获得契约所绑定类
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }
        //若该类绑定时设置为共享则缓存至instances单例数组
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }
        $this->fireResolvingCallbacks($abstract, $object);
        $this->resolved[$abstract] = true;
        array_pop($this->with);
        return $object;
    }

  前面都是在检测上下文绑定的这个属于契约接口的调动暂时不用看重点在于getConcrete方法获取到闭包之后直接进入了build方法

public function build($concrete)
    {
        //若传入的是一个闭包则直接通过闭包实例化类
        //这种闭包一般由provider类在laravel应用初始化阶段通过bind方法进行绑定。
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }
        //制造一个类反射
        $reflector = new ReflectionClass($concrete);
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }
        //将当前所实例化的类存入栈
        $this->buildStack[] = $concrete;
        //获得该类构造方法
        $constructor = $reflector->getConstructor();.
        //构造函数没有参数则直接实例化
        if (is_null($constructor)) {
            array_pop($this->buildStack);
            return new $concrete;
        }
        //若有构造函数则获取其参数
        $dependencies = $constructor->getParameters();
        //运行构造函数并解决依赖
        $instances = $this->resolveDependencies(
            $dependencies
        );
        //解决完依赖出栈
        array_pop($this->buildStack);
        return $reflector->newInstanceArgs($instances);
    }

  还记得app.php中一个简单绑定把Illuminate\Contracts\Http\Kernel::class绑定为了App\Http\Kernel::class类吗

  当build执行到kernel构造函数的时候我们看一下

public function __construct(Application $app, Router $router)
    {
        $this->app = $app;
        //路由类实例由容器自动加载依赖而来
        $this->router = $router;
        //系统中间件
        $router->middlewarePriority = $this->middlewarePriority;
        //中间件分组
        foreach ($this->middlewareGroups as $key => $middleware) {
            $router->middlewareGroup($key, $middleware);
        }
        //注册中间件别名
        foreach ($this->routeMiddleware as $key => $middleware) {
            $router->aliasMiddleware($key, $middleware);
        }
    }

  可以看到laravel在实例化kernel的时候kernel的构造函数依赖了application和route两个对象并将自身的middlewareGroup和routeMiddleware数组解析到了route对象里在路由进行调用的时候就会吧路由方法上绑定的中间件名在这里给解析出来其中routeMiddleware为别名所用

第五步
  随后在index.php里面利用kernel的handle方法传入了一个request对象来处理这次的网页请求

//引导数组
    protected $bootstrappers = [
        \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
        \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
        \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
        \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
        \Illuminate\Foundation\Bootstrap\BootProviders::class,
    ];
public function handle($request)
    {
        try {
            //启用http方法覆盖参数
            $request->enableHttpMethodParameterOverride();
            //通过路由发送请求
            $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;
    }
protected function sendRequestThroughRouter($request)
    {
        //将请求存入容器
        $this->app->instance(‘request’, $request);
        //清除facade门面
        Facade::clearResolvedInstance(‘request’);
        //初始化引导
        $this->bootstrap();
        //让请求进入中间件
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

  上面bootstrap中会分别执行每一个bootstrapper的bootstrap方法来引导启动应用程序的各个部分

  1. DetectEnvironment 检查环境

  2. LoadConfiguration 加载应用配置

  3. ConfigureLogging 配置日至

  4. HandleException 注册异常处理的Handler

  5. RegisterFacades 注册Facades

  6. RegisterProviders 注册Providers

  7. BootProviders 启动Providers

  启动应用程序app的最后两步就是注册服务提供者和启动服务提供者先来看一下注册服务提供器服务提供器的注册类由\Illuminate\Foundation\Bootstrap\RegisterProviders::class负责该类用于加载所有服务提供器的register函数并保存延迟加载的服务的信息以便实现延迟加载

  所有的服务提供者都在配置文件app.php文件的providers数组里面类ProviderRepository负责所有服务加载功能loadManifest()方法回家再服务提供器缓存文件services.php如果是框架第一次启动则没有这个文件或者是缓存文件中的providers数组项与config/app.php文件里得providers数组不一致都会编译生成services.php

  application的registerConfiguredProviders()方法对服务提供者进行了注册通过框架的文件系统收集了配置文件中的各种provicers并转化成数组在vendor\laravel\framework\src\Illuminate\Foundation\ProviderRepository.php类的load方法中进行加载但最终还是会在application类中的register()方法中通过字符串的方式new出对象在执行provider中自带的register()方法

public function load(array $providers)
    {
        $manifest = $this->loadManifest();
        // First we will load the service manifest, which contains information on all
        // service providers registered with the application and which services it
        // provides. This is used to know which services are “deferred” loaders.
        if ($this->shouldRecompile($manifest, $providers)) {
            $manifest = $this->compileManifest($providers);
        }
        // Next, we will register events to load the providers for each of the events
        // that it has requested. This allows the service provider to defer itself
        // while still getting automatically loaded when a certain event occurs.
        foreach ($manifest[‘when’] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }
        // We will go ahead and register all of the eagerly loaded providers with the
        // application so their services can be registered with the application as
        // a provided service. Then we will set the deferred service list on it.
        foreach ($manifest[‘eager’] as $provider) {
            $this->app->register($provider);
        }
        $this->app->addDeferredServices($manifest[‘deferred’]);
    }

  太多支线的细节不用深挖重点在于让请求进入中间件这里它用了一个管道模式或者说装饰模式通过函数调用栈的形式对请求进行过滤这个等到后面中间件的时候单独说最终通过了所有中间件的请求会进入到Illuminate\Routing\router类的dispatchToRoute方法

public function __construct(Dispatcher $events, Container $container = null)
    {
        $this->events = $events;
        $this->routes = new RouteCollection;
        $this->container = $container ?: new Container;
    }
/**
     * Dispatch the request to the 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);
    }
    /**
     * Dispatch the request to a route and return the response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    public function dispatchToRoute(Request $request)
    {
        return $this->runRoute($request, $this->findRoute($request));
    }

  router类里的runRouteWithinStack方法通过管道的方式运行了系统自带中间件。

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()
                            );
                        });
    }

  这些中间件里有一个laravel\framework\src\Illuminate\Routing\Middleware\SubstituteBindings.php中间件用于处理路由上的绑定。其中调用了Illuminate\Routing\router类中的substituteImplicitBindings方法对路由上的模型进行了绑定。

class SubstituteBindings
{
    /**
     * The router instance.
     *
     * @var \Illuminate\Contracts\Routing\Registrar
     */
    protected $router;
    public function __construct(Registrar $router)
    {
        $this->router = $router;
    }
    public function handle($request, Closure $next)
    {
        $this->router->substituteBindings($route = $request->route());
        $this->router->substituteImplicitBindings($route);
        return $next($request);
    }
}

  在Illuminate\Routing\RouteSignatureParameters.php中通过对路由route中的控制器字符串或闭包函数进行反射获取到他们的参数名与类型提示并过滤出Illuminate\Contracts\Routing\UrlRoutable类的子类过滤后得到的便是模型的类型提示了。之后又在Illuminate\Routing\ImplicitRouteBinding.php类中通过容器的make方法将反射得到的类名实例化为对象使用model中的resolveRouteBinding方法通过路由参数获取数据对象而后在route类中赋值给route属性。Illuminate\Routing\Route类的runCallable方法里对路由进行了调用。控制器和方法是从路由文件中获取到的(通过symfony的request对象获取到pathinfo)依然是通过字符串解析为类名和方法名随后通过ioc容器实例化类为对象再调用控制器基类的某个方法执行传入的方法名Illuminate\Routing\ControllerDispatcher类的dispatch方法为真正执行的部分其中resolveClassMethodDependencies方法会对控制器的参数实行依赖注入。传入从路由中获取的参数与从控制器反射中获取的方法参数。如果该方法所需的参数不是一个模型绑定则会通过容器中的make方法获取对象实例。

public function dispatch(Route $route, $controller, $method)
    {
        //解析类方法的依赖
        $parameters = $this->resolveClassMethodDependencies(
            $route->parametersWithoutNulls(), $controller, $method
        );
        //若控制器中存在回调
        if (method_exists($controller, ‘callAction’)) {
            return $controller->callAction($method, $parameters);
        }
        //调用控制器方法
        return $controller->{$method}(…array_values($parameters));
    }

  最后控制器返回执行后的结果被response类包装成响应对象返回至index.php通过send方法发送至浏览器。

来源:CSDN https://blog.csdn.net/TyphoonHao/article/details/91870782

转载请注明出处:https://www.onexin.net/laravel-request-kernel-response/

相关文章:

1、Laravel核心解读–服务容器(IocContainer)
https://www.onexin.net/laravel-ioccontainer/

2、Discuz!Q 如何基于 Laravel Valet 运行 Discuz! Q ?
https://www.onexin.net/discuzq-laravel-valet-discuz-q/

3、Laravel 8.x 简体中文最新手册指南 – 安装
https://www.onexin.net/laravel-8-x-docs-installation/

4、Statamic 3已发布且基于Laravel软件包构建
https://www.onexin.net/laravel-statamic-3/

5、Sends a http request to www.example.com
https://www.onexin.net/sends-an-http-request-to-www-example-com/

Leave a Reply