当前位置: 移动技术网 > IT编程>开发语言>PHP > Laravel源码分析--Laravel生命周期详解

Laravel源码分析--Laravel生命周期详解

2018年12月01日  | 移动技术网IT编程  | 我要评论

 

一、xdebug调试

这里我们需要用到php的 xdebug 拓展,所以需要小伙伴们自己去装一下,因为我这里用的是docker,所以就简单介绍下在docker中使用xdebug的注意点。

1、在phpstorm中的 perferences >> languages & framework >> php >> debug >> dbgp proxy 中的host填写的是宿主机的ip地址。可以在命令行中使用ifconfig / ipconfig查看你的本地ip。

2、在 perferences >> languages & framework >> php >> servers 中将你本地项目地址映射到docker容器中的项目地址。

3、xdeug.ini的配置。需要特别注意的就是  xdebug.remote_host  填的也是你的宿主机ip。 xdebug.remote_connect_back 配置一定要关掉,否则 xdebug.remote_host 就会不起作用。

当xdebug启动后,效果是这样的

 

 

二、laravel生命周期

1、启动容器

我们知道laravel所有的请求都会被nginx转发到项目下的 public/index.php 文件中。

<?php

/**
 * laravel - a php framework for web artisans
 *
 * @package  laravel
 * @author   taylor otwell <taylor@laravel.com>
 */

define('laravel_start', microtime(true));

/*
|--------------------------------------------------------------------------
| register the auto loader
|--------------------------------------------------------------------------
|
| composer provides a convenient, automatically generated class loader for
| our application. we just need to utilize it! we'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. it feels great to relax.
|
*/

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

/*
|--------------------------------------------------------------------------
| turn on the lights
|--------------------------------------------------------------------------
|
| we need to illuminate php development, so let us turn on the lights.
| this bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

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

/*
|--------------------------------------------------------------------------
| run the application
|--------------------------------------------------------------------------
|
| once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/

$kernel = $app->make(illuminate\contracts\http\kernel::class);

$response = $kernel->handle(
    $request = illuminate\http\request::capture()
);

$response->send();

$kernel->terminate($request, $response);

很显然,第一件事就是 require __dir__.'/../vendor/autoload.php' ,自动加载的加载。

核心 vendor/composer/classloader 类中的 findfile 方法

    public function findfile($class)
    {
        // class map lookup
        if (isset($this->classmap[$class])) {
            return $this->classmap[$class];
        }
        if ($this->classmapauthoritative || isset($this->missingclasses[$class])) {
            return false;
        }
        if (null !== $this->apcuprefix) {
            $file = apcu_fetch($this->apcuprefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findfilewithextension($class, '.php');

        // search for hack files if we are running on hhvm
        if (false === $file && defined('hhvm_version')) {
            $file = $this->findfilewithextension($class, '.hh');
        }

        if (null !== $this->apcuprefix) {
            apcu_add($this->apcuprefix.$class, $file);
        }

        if (false === $file) {
            // remember that this class does not exist.
            $this->missingclasses[$class] = true;
        }

        return $file;
    }

 

当自动加载注册完后,就开始启动laravel了,下面 $app = require_once __dir__.'/../bootstrap/app.php' 就是返回一个app实例,我们看下这文件到底做了些啥。

<?php/*
|--------------------------------------------------------------------------
| create the application
|--------------------------------------------------------------------------
|
| the first thing we will do is create a new laravel application instance
| which serves as the "glue" for all the components of laravel, and is
| the ioc container for the system binding all of the various parts.
|
*/

$app = new illuminate\foundation\application(
    realpath(__dir__.'/../')
);

/*
|--------------------------------------------------------------------------
| bind important interfaces
|--------------------------------------------------------------------------
|
| next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. the kernels serve the
| incoming requests to this application from both the web and cli.
|
*/

$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 the application
|--------------------------------------------------------------------------
|
| this script returns the application instance. the instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/

return $app;

首先new出一个app实例,但在其构造函数中,做了一些框架的初始化工作。

public function __construct($basepath = null)
    {
        if ($basepath) {
            $this->setbasepath($basepath);
        }

        $this->registerbasebindings();

        $this->registerbaseserviceproviders();

        $this->registercorecontaineraliases();
    }

在 setbasepath 中,向laravel容器中注入了下面这些路径。

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

这也就是为什么我们在项目中可以使用 storage_path() 等方法获取路径的原因。

然后就是注册一些laravel的基础绑定

protected function registerbasebindings()
    {
        static::setinstance($this);

        $this->instance('app', $this);

        $this->instance(container::class, $this);

        $this->instance(packagemanifest::class, new packagemanifest(
            new filesystem, $this->basepath(), $this->getcachedpackagespath()
        ));
    }

这里将app容器绑定到 app 和 container::class 上,然后再绑定一个 packagemanifest::class 到容器中,这个类是在laravel5.5后的新功能--包自动发现时用的。

然后注册了三个服务提供者,如果它们中有register方法,则执行其中的register方法。

   protected function registerbaseserviceproviders()
    {
        $this->register(new eventserviceprovider($this));

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

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

至于它们的register方法都做了些啥,感兴趣的同学可以去看下源码。最后 registercorecontaineraliases() 方法就是给大多类注册了一些别名/简称。

以上这些动作我们都可以通过xdebug来看到。让我们看下app实例中现在都有那些动作已经完成了。

这是上面 registerbaseserviceproviders() 注册的三个服务提供者。

这个是上面注册的三个服务提供者里面register方法绑定到容器中的。

这是 registerbasebindings 方法中绑定。

这两个属性是 registercorecontaineraliases 方法中绑定的一些别名/简称。

然后连续三次调用 $app->singleton() ,分别将http,console和异常处理的实现类绑定到容器中。可以在app实例的bindings属性中看到

然后返回app实例,到此,这就是 bootstrap/app.php 文件做的所有事情。

我们知道laravel的核心就是服务容器,在 bootstrap/app.php 文件中,我们向容器中绑定了很多服务,那么,我们想要服务该怎么从容器中取呢?

回到 public/index.php 中,紧接着 $kernel = $app->make(illuminate\contracts\http\kernel::class); ,就是从容器中取出 illuminate\contracts\http\kernel::class 类,此时,我们已经知道, illuminate\contracts\http\kernel::class 的实现类 app\http\kernel::class ,我们也可以在xdebug中看到。

这里试想下,如果不用服务容器,自己new一个app\http\kernel类出来,会发现里面有很多依赖,即使你最后new出来了,相比服务容器,那也太麻烦了。

服务容器是使用依赖注入和 reflectionclass 反射类实现的。

app\http\kernel父类的构造方法中就是将app\http\kernel类中$middlewaregroups和$routemiddleware数组中的中间件绑定到route::class中。

 

2、分发路由

后面调用的 $response = $kernel->handle( $request = illuminate\http\request::capture() ); 像一个大黑盒子一样,在里面完成了所有的路由分发,中间件检测等工作。

先看一下 $request = illuminate\http\request::capture() 方法;首先是通过php的超全局变量创建一个symfonyrequest,然后再将symfonyrequest转换laravel可用的 illuminate\http\request ,并丢入 handle() 方法中。

    /**
     * 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 (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;
    }

里面的核心就是 sendrequestthroughrouter() 方法,通过字面意思也可以知道是--通过路由发送请求。

    /**
     * send the given request through the middleware / router.
     *
     * @param  \illuminate\http\request  $request
     * @return \illuminate\http\response
     */
    protected function sendrequestthroughrouter($request)
    {
        $this->app->instance('request', $request);

        facade::clearresolvedinstance('request');

        $this->bootstrap();

        return (new pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldskipmiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchtorouter());
    }

 $this->app->instance('request', $request); 和 facade::clearresolvedinstance('request'); 分别表示绑定 illuminate\http\request 到容器中和从facade(目前并没有)中删除request。

 $this->bootstrap(); 是将 illuminate\foundation\http\kernel 中的 $bootstrappers 数组

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,
    ];

放到app容器中的 $this->app->bootstrapwith($this->bootstrappers()); 方法中一一执行。

public function bootstrapwith(array $bootstrappers)
    {
        $this->hasbeenbootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

            $this->make($bootstrapper)->bootstrap($this);

            $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
        }
    }

该方法首先触发 'bootstrapping: '.$bootstrapper 事件,表明 $bootstrapper 正在执行。当执行完 $bootstrapper 中的 bootstrap() 的方法后,触发 'bootstrapped: '.$bootstrapper 时事件,表明 $bootstrapper 执行完毕。下面我们看下这个数组中到底做了什么。

1、 \illuminate\foundation\bootstrap\loadenvironmentvariables::class, 很显然,就是加载.env文件中的配置到项目中,它这里放在三个地方。

所以当我们想要获取.env中的配置文件时,可以使用getenv()、$_env$_server都可以。

2、 \illuminate\foundation\bootstrap\loadconfiguration::class, 同样的,这是加载config文件下的所有配置文件。并将这些配置存在 illuminate\config\repository 类的 $items 中。

3、 \illuminate\foundation\bootstrap\handleexceptions::class, 加载异常处理类

4、 \illuminate\foundation\bootstrap\registerfacades::class, 注册  config/app.php  中的 aliases 数组和 bootstrap/cache/packages.php 的所有门面。

5、 \illuminate\foundation\bootstrap\registerproviders::class, 注册 config/app.php 中的 providers 和 bootstrap/cache/packages.php 中的所有服务提供者,并将即使加载的和延迟加载的分开。记住,注册服务提供者时,如果该服务提供者有 register() 方法,则执行。

6、 \illuminate\foundation\bootstrap\bootproviders::class, 如果存在的话,则执行上一步中注册的所有服务提供者中的 boot() 方法。

执行完 bootstrap() 方法。laravel所有的加载工作都完成了,后面就开始通过 pipeline 分发路由了。

return (new pipeline($this->app))
                    ->send($request)  // 设置需要分发的request
                    ->through($this->app->shouldskipmiddleware() ? [] : $this->middleware)  // 设置request需要通过的中间件
                    ->then($this->dispatchtorouter());  // 开始通过中间件

重点就是在这个then()方法中。

    /**
     * run the pipeline with a final destination callback.
     *
     * @param  \closure  $destination
     * @return mixed
     */
    public function then(closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes), $this->carry(), $this->preparedestination($destination)
        );

        return $pipeline($this->passable);
    }

形象点,可以用包洋葱来理解这里到底做了些什么?

 array_reduce() 里就是将 app\http\kernel 中 $middleware 数组倒过来,通过 $this->carry() 将 $this->preparedestination($destination) 像包洋葱一样一层层包起来。

可以通过xdebug看下最终包好的洋葱的样子。

洋葱包完了,我们就该剥洋葱了。 return $pipeline($this->passable); 就是剥洋葱的整个动作。

具体怎么个剥法,可以在 carry() 方法中看到

    protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                if (is_callable($pipe)) {
                    // if the pipe is an instance of a closure, we will just call it directly but
                    // otherwise we'll resolve the pipes out of the container and call it with
                    // the appropriate method and arguments, returning the results back out.
                    return $pipe($passable, $stack);
                } elseif (! is_object($pipe)) {
            // $pipe是中间件的类名所以会进入这里,parsepipestring方法是解析中间件有没有携带参数,如果没有则$parameters是一个空数组。 list($name, $parameters) = $this->parsepipestring($pipe); // if the pipe is a string we will parse the string and resolve the class out // of the dependency injection container. we can then build a callable and // execute the pipe function giving in the parameters that are required. $pipe = $this->getcontainer()->make($name);    // 从容器中解析出中间件类             
            // $passable就是request, $stack就是包洋葱时包进来的闭包   $parameters = array_merge([$passable, $stack], $parameters); } else { // if the pipe is already an object we'll just make a callable and pass it to // the pipe as-is. there is no need to do any extra parsing and formatting // since the object we're given was already a fully instantiated object. $parameters = [$passable, $stack]; }
          // 如果中间件中有$this->method方法(其实就是handle方法),则传入$parameters参数,执行handle方法。 $response = method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters); return $response instanceof responsable ? $response->toresponse($this->container->make(request::class)) : $response; }; };

一直到洋葱芯,其他外层都是中间件的处理,我们找其中一个看一下,例如 \app\http\middleware\checkformaintenancemode::class, 的handle方法。

    /**
     * handle an incoming request.
     *
     * @param  \illuminate\http\request  $request
     * @param  \closure  $next
     * @return mixed
     *
     * @throws \symfony\component\httpkernel\exception\httpexception
     */
    public function handle($request, closure $next)
    {
     // 判断项目是否下线 if ($this->app->isdownformaintenance()) { $data = json_decode(file_get_contents($this->app->storagepath().'/framework/down'), true);        // 验证ip白名单 if (isset($data['allowed']) && iputils::checkip($request->ip(), (array) $data['allowed'])) { return $next($request); }        // 验证path是否在 $except 数组中 if ($this->inexceptarray($request)) { return $next($request); } throw new maintenancemodeexception($data['time'], $data['retry'], $data['message']); }      // 如果通过,则传request到闭包继续执行 return $next($request); }

所有的中间件处理方式都是一样的,具体哪个中间件做了哪些操作,感兴趣的同学可以自己去看下源码,这里不详细赘述。下面我们直接看洋葱芯都做了些啥?

其实洋葱芯就是 dispatchtorouter() 方法中返回的一个闭包。

   /**
     * get the route dispatcher callback.
     *
     * @return \closure
     */
    protected function dispatchtorouter()
    {
        return function ($request) {
       // 向容器中放入实例request $this->app->instance('request', $request);
       // 通过\illuminate\routing\router的dispatch方法分发request
       return $this->router->dispatch($request); };
   }

这个闭包里就是分发路由,然后再次像之前包洋葱和剥洋葱一样验证request中间件组里的中间件和路由配置里的中间件,最后执行控制器对应的方法。

至此laravel整个生命周期就结束了。撒花

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网