laravel5.5源码解析(一、入口应用的初始化)

laravel的项目入口文件index.php如下

 1 define(‘LARAVEL_START‘, microtime(true));
 2
 3 require __DIR__.‘/../vendor/autoload.php‘;
 4
 5 $app = require_once __DIR__.‘/../bootstrap/app.php‘;
 6
 7 $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
 8
 9 $response = $kernel->handle(
10     $request = Illuminate\Http\Request::capture()
11 );
12
13 $response->send();
14
15 $kernel->terminate($request, $response);

第一句记录了项目开始运行时间。

第二句引入了基于composer的自动加载模块。

第三句引入了laravel应用主体。

第四句创建了一个用于处理请求的核心。

第五句对实例化后的request对象进行解析并返回执行后的response响应对象。

第六句将响应内容进行输出。

第七句结束应用并释放资源。

关于第二句,这里我先解释一下自动加载,我们都知道php中如果要使用文件外的代码,需要使用require等方法先将文件引入,然后就可以使用被引入那个文件的代码了。但是我们平时使用框架编写代码的时候就不需要这么做了,只需要use命名空间,便可以直接new出对象了,这就要归功于自动加载了,php在new出当前不存在的对象时,会触发__autoload、spl_autoload等一些魔术方法。tp3的处理方式遍是非常粗暴的,在autoload魔术方法中,将当前类use的命名空间与我们new的对象名进行字符串拼接,随后require该文件就完了。laravel使用了composer显然就高级的多,不过再怎么高级,composer本身也是做了类似的操作,所以它也使用了spl_autoload函数,它高级在哪呢?我们都知道composer使用时可以新建一个json文件将需要的依赖编写在里面,composer运行时就会自动下载这些文件了。用composer 做自动加载也是一样,它将json文件里写入的依赖进行缓存成了key/value的关联数组,触发spl_autoload函数的时候便根据这些映射来require。存放在laravel\vendor\composer\autoload_classmap.php文件内,有兴趣的朋友可自行观看,这里不是重点,便到此为止了。(我初学php面向对象的时候一直以为命名空间下面那些use就是用来替代require、include的。直到后来学习mvc概念的时候自己试着做了个微框架的demo,才搞清楚use只是起到明确命名空间的作用。)

我们都知道,通常一个web程序所做的事,不外乎这么几点:

1、用户从浏览器进行请求,请求

2、程序接到请求开始运算,网页程序

3、运算结果渲染成网页返回给浏览器,网页响应

我们所写的大量代码都只是为了更好、更快、更方便的去做这3件事而已。

index文件中的$request、$kernel、$response这三个变量就分别与这三点进行对应了。laravel中也将请求、响应、和计算进行了分离,请求部分使用syfmony的request组件对浏览器发出的请求头等信息进行收集打包,形成一个对象来方便我们操作。$kernel算是laravel的请求处理核心了,通过request里的url找到相应路由的控制器,执行后返回视图等响应,并将$response输出至浏览器。知道大概流程后我们来看laravel的核心部分。

第三句代码就是在引入laravel的应用了,我们跳到G:\wamp64\www\test\laravel55\bootstrap\app.php文件内,我们会发现这个文件所做的事情也不多,只是new了一个application对象,调用了三次singleton方法,便将application实例给返回到index文件中了。(而这里new对象的时候整个文件都没有写require等代码,这就是通过composer进行的自动加载起作用了。)application文件位于G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Foundation\Application.php,new application时传入了当前的路径到它的构造方法里,它的构造方法执行了setBasePath、registerBaseBindings、registerBaseServiceProviders、registerCoreContainerAliases这几个方法。

setBasePath:就是将各个系统关键类的路径存储在了app容器对象里,跟踪到bindPathsInContainer方法里,我们会发现如下所示的,存储的路径,具体的实现代码在其父类Container类的instance方法中,代码很简单就一句是$this->instances[$abstract] = $instance;。大家可以在Application类的setBasePath方法之后使用dd()打印一下$this看看它的instance属性

 1     protected function bindPathsInContainer()
 2     {
 3         $this->instance(‘path‘, $this->path());
 4         $this->instance(‘path.base‘, $this->basePath());
 5         $this->instance(‘path.lang‘, $this->langPath());
 6         $this->instance(‘path.config‘, $this->configPath());
 7         $this->instance(‘path.public‘, $this->publicPath());
 8         $this->instance(‘path.storage‘, $this->storagePath());
 9         $this->instance(‘path.database‘, $this->databasePath());
10         $this->instance(‘path.resources‘, $this->resourcePath());
11         $this->instance(‘path.bootstrap‘, $this->bootstrapPath());
12     }

registerBaseBindings:这个方法做的事情和上一个差不多,将$this,application对象及它的父类存入了instance属性中,分别起了app和Container两个名字。将vendor路径与bootstrap/cache/packages.php里的providers服务提供者路径传入PackageManifest类中,并绑定在了app对象的instance中,大家在这个方法后dd($this)会发现跟setBasePath一样,instance属性中多了几条,只是其中三个是对象而已。

registerBaseServiceProviders:注册了基本的providers,event事件服务提供者、log日志服务提供者、routing路由部分服务提供者。服务提供者的部分会在后面解释,现在把它看做是一个功能模块的入口就可以了。同样的,我们在这个方法后面dd($this)会发先serviceProviders属性与loadedProviders属性增加了对应的值。bindings属性也增加了provider相应的boot闭包,闭包中存储的是实例化对象的代码,运行后会得到一个对象实例,以闭包的形式存储来实现按需加载。

registerCoreContainerAliases:跟它的名字说的一样,只是注册了容器的核心类别名,同样打印后发现在aliases、abstractAliases属性中增加了相应的映射数组。以后会根据这个别名来方便的实例化对象,这个列表太长我就不放图了

好的,总结一下,application类初始化的时候它做了这么些工作:

1、设置路径  2、绑定了app对象和packages包的实例 3、注册了基本服务提供者 4、增加了核心类的别名  全都是一些配置工作。

好,回到app.php文件,$app执行了三个singleton方法,通过注释我们可以知道它绑定了一些重要的接口道容器中。我们点击跳转后一路跟踪到G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Container\Container.php文件的bind方法中看着很长的代码,其实都是状态判断,这个函数所做的事情还是讲传入的类名路径转换为一个启动服务的闭包,并保存在容器的bindings属性中。见下方代码,getClosure方法也可以看一下,比较简单。

 1     public function bind($abstract, $concrete = null, $shared = false)
 2     {
 3         //抽象类型判断
 4         $this->dropStaleInstances($abstract);
 5
 6         if (is_null($concrete)) {
 7             $concrete = $abstract;
 8         }
 9
10         //这一阶段重点,刚刚我们index传入的类路径不是闭包,就会在这里被getClosure方法转换成一个返回对象实例的闭包了
11         if (! $concrete instanceof Closure) {
12             $concrete = $this->getClosure($abstract, $concrete);
13         }
14         //将闭包绑定在bindings属性中
15         $this->bindings[$abstract] = compact(‘concrete‘, ‘shared‘);
16
17         // If the abstract type was already resolved in this container we‘ll fire the
18         // rebound listener so that any objects which have already gotten resolved
19         // can have their copy of the object updated via the listener callbacks.
20         if ($this->resolved($abstract)) {
21             $this->rebound($abstract);
22         }
23     }    

我们在app.php文件的singleton之后再次dd($app)会发现bindings属性中多出了几个相应的属性,见下图,其中http\kernel用来处理http请求,console\kernel用来处理artisan命令行请求,debug\exceptionHandler便是处理异常错误的了。

app.php文件看完了,我们再回到index.php文件,第四行laravel制造了一个kernel实例,还记得刚刚在app.php文件时,我们通过singleton绑定的那个闭包函数吗?这里马上就派上用场了,make顾名思义就制造,这个方法通过类名路径或别名返回一个对象实例(对,还记得刚刚application对象构造函数绑定了一大堆别名吗)。G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Foundation\Application.php类的make方法

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

G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Container\Container.php在container类的make方法就开始从容器中解析类了,一开始那一大段都是检测上下文绑定的,这个属于契约接口的动态调用,暂时可以不去看它,重点在于从getConcrete方法获取到闭包后,直接进入了build构建方法。

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

    protected function resolve($abstract, $parameters = [])
    {
        $abstract = $this->getAlias($abstract);
        //是否存在构建上下文,此出为了服务提供者的契约
        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );

        // If an instance of the type is currently being managed as a singleton we‘ll
        // just return an existing instance instead of instantiating new instances
        // so the developer can keep using the same objects instance every time.
        //instances数组中有该类,并且不需要构建上下文的话,便直接返回该类实例
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }
        //将实例化类所需的参数存入数组
        $this->with[] = $parameters;
        //获取该类闭包,若无则还是返回类名字符串
        $concrete = $this->getConcrete($abstract);

        // We‘re ready to instantiate an instance of the concrete type registered for
        // the binding. This will instantiate the types, as well as resolve any of
        // its "nested" dependencies recursively until all have gotten resolved.
        //若当前所make的类没有上下文绑定,并且是一个闭包则直接进行构建,否则再次递归make方法获得契约所绑定类
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        // If we defined any extenders for this type, we‘ll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // If the requested type is registered as a singleton we‘ll want to cache off
        // the instances in "memory" so we can return it later without creating an
        // entirely new instance of an object on each subsequent request for it.
        //若该类绑定时设置为共享,则缓存至instances单例数组
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        $this->fireResolvingCallbacks($abstract, $object);

        // Before returning, we will also set the resolved flag to "true" and pop off
        // the parameter overrides for this build. After those two things are done
        // we will be ready to return back the fully constructed class instance.
        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }

还记得刚刚在app.php文件中有一个singleton函数将Illuminate\Contracts\Http\Kernel::class绑定为了App\Http\Kernel::class类吗?当build方法执行到kernel的构造函数时,跳转到其父类G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php看一看

 1     public function build($concrete)
 2     {
 3         // If the concrete type is actually a Closure, we will just execute it and
 4         // hand back the results of the functions, which allows functions to be
 5         // used as resolvers for more fine-tuned resolution of these objects.
 6         //若传入的是一个闭包则直接通过闭包实例化类,这种闭包一般由provider类在laravel应用初始化阶段通过bind方法进行绑定。
 7         if ($concrete instanceof Closure) {
 8             return $concrete($this, $this->getLastParameterOverride());
 9         }
10         //制造一个类反射
11         $reflector = new ReflectionClass($concrete);
12
13         // If the type is not instantiable, the developer is attempting to resolve
14         // an abstract type such as an Interface of Abstract Class and there is
15         // no binding registered for the abstractions so we need to bail out.
16         if (! $reflector->isInstantiable()) {
17             return $this->notInstantiable($concrete);
18         }
19         //将当前所实例化的类存入栈
20         $this->buildStack[] = $concrete;
21         //获得该类构造方法
22         $constructor = $reflector->getConstructor();
23
24         // If there are no constructors, that means there are no dependencies then
25         // we can just resolve the instances of the objects right away, without
26         // resolving any other types or dependencies out of these containers.
27         //构造函数没有参数则直接实例化
28         if (is_null($constructor)) {
29             array_pop($this->buildStack);
30
31             return new $concrete;
32         }
33         //若有构造函数则获取其参数
34         $dependencies = $constructor->getParameters();
35
36         // Once we have all the constructor‘s parameters we can create each of the
37         // dependency instances and then use the reflection instances to make a
38         // new instance of this class, injecting the created dependencies in.
39         //运行构造函数,并解决依赖
40         $instances = $this->resolveDependencies(
41             $dependencies
42         );
43         //解决完依赖,出栈
44         array_pop($this->buildStack);
45
46         return $reflector->newInstanceArgs($instances);
47     }

    use Illuminate\Routing\Router;

    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两个对象。并将自身的$middlewareGroups、routeMiddleware数组解析进了route对象里,在路由进行调用的时候就会把路由方法上绑定的中间件名在这里解析出实例来调用了,其中routeMiddleware为别名所用。,随后在index.php文件中马上就利用kernel的handle方法,传入了一个request对象,来处理这次的网页url请求。

    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());
    }
    //引导数组
    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,
    ];

上面bootstrap中会分别执行每一个bootstrapper的bootstrap方法来引导启动应用程序的各个部分
1. DetectEnvironment 检查环境
2. LoadConfiguration 加载应用配置
3. ConfigureLogging 配置日至
4. HandleException 注册异常处理的Handler
5. RegisterFacades 注册Facades
6. RegisterProviders 注册Providers
7. BootProviders 启动Providers

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

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

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

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

router类里的runRouteWithinStack方法通过管道的方式,运行了系统自带中间件。这些中间件里有一个laravel\framework\src\Illuminate\Routing\Middleware\SubstituteBindings.php中间件,用于处理路由上的绑定。其中调用了Illuminate\Routing\router类中的substituteImplicitBindings方法对路由上的模型进行了绑定。在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方法获取对象实例。

 1     public function dispatch(Route $route, $controller, $method)
 2     {
 3         //解析类方法的依赖
 4         $parameters = $this->resolveClassMethodDependencies(
 5             $route->parametersWithoutNulls(), $controller, $method
 6         );
 7         //若控制器中存在回调
 8         if (method_exists($controller, ‘callAction‘)) {
 9             return $controller->callAction($method, $parameters);
10         }
11         //调用控制器方法
12         return $controller->{$method}(...array_values($parameters));
13     }

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

原文地址:https://www.cnblogs.com/wyycc/p/9823491.html

时间: 2024-10-29 06:35:31

laravel5.5源码解析(一、入口应用的初始化)的相关文章

十七.jQuery源码解析之入口方法Sizzle(1)

函数Sizzle(selector,context,results,seed)用于查找与选择器表达式selector匹配的元素集合.该函数是选择器引擎的入口. 函数Sizzle执行的6个关键步骤如下: 1.解析选择器表达式,解析出块表达式和关系符. 2.如果存在位置伪类,则从左向右查找: a.查找第一个块表达式匹配的元素集合,得到第一个上下文元素集合. b.遍历剩余的块表达式和块间关系符,不断缩小上下文元素集合. 3.否则从右向左查找: a.查找最后一个块表达式匹配的元素集合,得到候选集,映射集

Spring IoC源码解析——Bean的创建和初始化

Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和AOP的容器框架,主要是针对JavaBean的生命周期进行管理的轻量级容器,可以单独使用,也可以和Struts框架,MyBatis框架等组合使用. IoC介绍 IoC是什么 Ioc-Inversion of Control,即"控制反转",不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控

SpringMVC源码解析-DispatcherServlet启动流程和初始化

在使用springmvc框架,会在web.xml文件配置一个DispatcherServlet,这正是web容器开始初始化,同时会在建立自己的上下文来持有SpringMVC的bean对象. 先从DispatcherServlet入手,从名字来看,它是一个Servlet.它的定义如下: public class DispatcherServlet extends FrameworkServlet { 它是继承FrameworkServlet,来看一下整个的继承关系. 从继承关系来看,Dispatc

laravel5.5源码阅读草稿——入口

laravel的启动需要通过路由.中间件.控制器.模型.视图最后出现在浏览器.而路由.中间件.模型,这些功能都有自己的类,比如Route::any().DB::table().$this->middleware()等等,这些功能都是由一个叫IOC(服务容器)的对象来调配的. 它就像框架里的一个管家,我们需要某些功能的时候不需要去自己new.去考虑运行这A对象还需要把哪些对象传入A对象里才能运行了.laravel的index入口文件只管制造一个ioc实例,然后把request对象传入其中. ioc

Laravel源码解析--看看Lumen到底比Laravel轻在哪里

在前面一篇<Laravel源码解析--Laravel生命周期详解>中我们利用xdebug详细了解了下Laravel一次请求中到底做了哪些处理.今天我们跟 Lumen 对比下,看看 Lumen 比 Laravel 轻在哪里? 1.Lumen生命周期 相比于Laravel,在Lumen中,你对框架有着更多的控制权.Lumen的入口文件相比于Laravel要简单许多. <?php /* |-----------------------------------------------------

gulp源码解析(二)—— vinyl-fs

在上一篇文章我们对 Stream 的特性及其接口进行了介绍,gulp 之所以在性能上好于 grunt,主要是因为有了 Stream 助力来做数据的传输和处理. 那么我们不难猜想出,在 gulp 的任务中,gulp.src 接口将匹配到的文件转化为可读(或 Duplex/Transform)流,通过 .pipe 流经各插件进行处理,最终推送给 gulp.dest 所生成的可写(或 Duplex/Transform)流并生成文件. 本文将追踪 gulp(v4.0)的源码,对上述猜想进行验证. 为了分

ExcelReport第二篇:ExcelReport源码解析

导航 目   录:基于NPOI的报表引擎--ExcelReport 上一篇:使用ExcelReport导出Excel 下一篇:扩展元素格式化器 概述 针对上一篇随笔收到的反馈,在展开对ExcelReport源码解析之前,我认为把编写该组件时的想法分享给大家是有必要的. 编写该组件时,思考如下: 1)要实现样式.格式与数据的彻底分离. 为什么要将样式.格式与数据分离呢?恩,你不妨想一想在生成报表时,那些是变的而那些又是不变的.我的结论是:变的是数据. 有了这个想法,很自然的想到用模板去承载不变的部

spring mvc源码解析

1.从DispatcherServlet开始     与很多使用广泛的MVC框架一样,SpringMVC使用的是FrontController模式,所有的设计都围绕DispatcherServlet 为中心来展开的.见下图,所有请求从DispatcherServlet进入,DispatcherServlet根据配置好的映射策略确定处理的 Controller,Controller处理完成返回ModelAndView,DispatcherServlet根据配置好的视图策略确定处理的 View,由V

第二章 Google guava cache源码解析1--构建缓存器

1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHashMap(或者说成就是一个ConcurrentHashMap,只是在其上多添加了一些功能) 2.使用实例 具体在实际中使用的例子,去查看<第七章 企业项目开发--本地缓存guava cache>,下面只列出测试实例: import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit;