laravel解析-入口应用初始化

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 $resopnse 就是对应着请求、计算、响应

请求部分使用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的应用程序对象,这是个关键,让我们看一下这个文件

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看一下

laravel解析-入口应用初始化_第1张图片

第二件:

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

laravel解析-入口应用初始化_第2张图片

 

第三件:

      registerBaseServiceProviders()方法

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

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

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

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

laravel解析-入口应用初始化_第3张图片

你户发现 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用来处理异常错误

laravel解析-入口应用初始化_第4张图片

 这时候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方法发送至浏览器。 

 

 

 

 

 

 

 

     

 

 

你可能感兴趣的:(laravel,laravel服务容器,学习)