Laravel的生命周期

万物皆有生命,每个生命都有自己的生命周期。

想要说清Laravel的生命周期,先来了解一下PHP的生命周期。

:meng:

PHP的生命周期

众所周知,PHP有两种运行模式:

  1. CLI模式
  2. web模式

当我们在命令行终端键入php这个命令的时候,使用的就是CLI模式;当使用nginx或者其他服务器作为宿主来处理一个请求的时候,会调用php来运行,此时使用的就是web模式。

PHP的两种运行模式都必将经历下面这几个阶段,才能完成一次请求处理。

php_module_startup() // 模块初始化阶段
php_request_startup() // 请求初始化阶段
php_request_shutdown() // 请求关闭阶段
php_module_shutdown() // 模块关闭阶段

当我们请求一个php文件的时候,比如laravel的public/index.php文件时,php为了完成此次请求,会发生5个阶段的生命周期切换:

  1. 模块初始化,即调用php.ini中指明的拓展初始化函数进行初始化工作,如mysql拓展。
  2. 请求初始化,即初始化本次执行脚本所需要的变量名称和变量值,如$_SESSION,$_COOKIE等
  3. 执行该php脚本
  4. 请求处理完成,按顺序调用各个模块的shutdown方法,并对每个变量进行unset()。
  5. 关闭模块,php调用每个拓展的shutdown方法,释放每个模块在内存中的占有。这也意味着没有下一个请求了。

:huaji22:

web模式和cli模式的区别

CLI模式会在每次脚本执行都需要经历完整的5个周期,因为脚本执行完不会再有下一个请求。

web模式为了应对并发,会采用多线程(php-fpm),因此周期中的1和5只执行一次,下次接收到请求时,重复2-4的周期,这样就节省了模块初始化带来的开销。

说了这么多,知道这些有什么用?其实就是为了定位Laravel在哪里执行的,没错,就是第3步。

现在我们知道了,每次请求之后php的变量都会unset(),laravel的singleton只是在某一次请求中singleton,在php中的静态变量也不能在多个请求之间共享,不像Java静态变量拥有全局作用。

Laravel的生命周期

官方文档(5.4):https://laravelacademy.org/post/6684.html

// 阶段一 
require __DIR__.'/../bootstrap/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);

上面是除却备注public/index.php全部源码,其实laravel最好的文档就是注释。

  1. 阶段一:加载所需的依赖,通过注释就可以得知
    /*
    |--------------------------------------------------------------------------
    | 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.
    |
    */
  2. 阶段二:创建laravel实例(服务器容器)
    这个阶段是由 bootstrap/app.php 来完成创建实例(服务器容器)的,实际就是项目初始化的过程,这个过程主要完成注册项目基础服务、注册项目服务提供者别名、注册目录路径等在内的一些列注册工作。
    // 创建应用实例

    $app = new Illuminate\Foundation\Application(
        realpath(__DIR__.'/../')
    );
    // 绑定内核到服务器容器
    // http内核
    $app->singleton(
        Illuminate\Contracts\Http\Kernel::class,
        App\Http\Kernel::class
    );
    // console内核
    $app->singleton(
        Illuminate\Contracts\Console\Kernel::class,
        App\Console\Kernel::class
    );
    // 异常处理内核
    $app->singleton(
        Illuminate\Contracts\Debug\ExceptionHandler::class,
        App\Exceptions\Handler::class
    );
    return $app;
  3. 阶段二:接收请求并响应
    $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

    这段代码主要是解析内核实例将我们项目目录中的中间件注册到路由器中,来实现http请求前的过滤功能;
    研究一下app\Http\Kernel中间件文件继承了HttpKernel,HttpKernel中的__construct()传递了两个参数,$app就是上面一步创建的服务器容器,$route就是我们的路由器。

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

    之前所有的步骤都是为了构建服务来处理请求,这个时候就构建完成可以处理请求了。

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

    通过Illuminate\Http\Request::capture()获取用户请求实例,拿到用户请求中的报文信息;还是HttpKernel这个类文件,$kernel->handle()拿到用户的请求数据后,返回一个响应实例。

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

    相信懂得MVC架构的都知道,处理请求并且获取响应都是通过控制器层来调度不同的模型层来处理请求和返回响应数据的,看到这里还是一脸懵逼,感觉没Controller什么事情啊?
    :huaji7:
    下面我们看看$this->sendRequestThroughRouter($request);这个方法

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

    通过代码来看一下处理请求及返回响应的步骤如下:

    • 先将请求实例注册到当前容器中
      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);
          }
      }
    • 清除之前的请求实例缓存
    • 启动引导程序
      启动引导程序中做了非常多的操作;例如:加载配置文件,注册别名类加载服务,注册服务提供者,启动服务。
      具体可研究一下HttpKernel类文件下的$bootstrappers变量和Illuminate\Foundation\Application中的bootstrapWith()方法。
    • 发送请求到路由器(通过路由找到控制器层)
      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());
      }

      在这段代码中,创建管道,并且将本次请求实例进行中间件处理后,执行通过路由找到控制器层或者匿名函数获取响应数据。

      protected function dispatchToRouter()
      {
          return function ($request) {
              $this->app->instance('request', $request);
      
              return $this->router->dispatch($request);
          };
      }

      $this->router->dispatch($request)对应Illuminate\Routing\Router类。
      Illuminate\Routing\Router类中完成了,查找到对应的路由实例,并运行路由实例中的控制器或者匿名函数(最终运行routers\web.php配置中匹配到的控制器或匿名函数)。
      至此,Laravel就完成了一次请求处理。

  4. 阶段四:返回响应数据
    经过上面漫长的处理之后,HTTP请求终于迎来了最终章,将得到的响应数据输出给用户。
    发送响应由 Illuminate\Http\Response 父类 Symfony\Component\HttpFoundation\Response 中的 send() 方法完成。

    public function send()
    {
        $this->sendHeaders();
        $this->sendContent();
    
        if (\function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
            static::closeOutputBuffers(0, true);
        }
    
        return $this;
    }
  5. 阶段五:终止程序
    $kernel->terminate($request, $response);// 主要清理本次请求注册的中间件

总结

上面的内容大致介绍了Laravel的生命周期,这样我们就可以更加了解Laravel的工作原理以及机制,开发出更加高效的代码。

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注