Laravel 框架源码分析---框架启动过程

在我们学习一个框架的过程中,了解一个框架的启动流程,对于我们理解、使用好框架具有很大帮助,今天我们就来看一下 laravel 框架启动过程。

框架启动过程中的相关类

在 laravel 启动过程中,主要涉及到以下类:

  1. Illuminate\Foundation\Application
    Application 是 laravel 框架最核心的类之一。它首先是一个 IOC 容器,管理整个框架类对象的定义、实例化、存储;同时它也是整个框架的应用类,管理应用中的 ServiceProvider,启动、停止应用,启动针对不同应用的启动器。

  2. App\Http\Kernel
    针对 http 请求的核心类,继承自 Illuminate\Foundation\Http\Kernel,管理框架中的路由,配置针对 http 应用的启动器,执行 http 请求,停止针对 http 请求的应用。

  3. Illuminate\Routing\Router
    框架路由系统的门面类,管理并配置路由,将请求分发到路由并返回路由执行请求的响应。

  4. Illuminate\Routing\Route
    框架路由系统的路由类,每个配置的路由对应类的一个实例,里面记录了匹配此路由请求的 uri、methods 等信息,以及对应的执行请求的控制器方法或者回调等信息,实现了判断一个请求是否匹配此路由的方法。

  5. Illuminate\Http\Request
    http 请求类,主要作用是封装 http 请求的query、server、input、 file、cookie、session 等参数。

  6. Illuminate\Http\Request
    http 响应类,构造框架的 http 响应,里面包括响应码、响应内容、响应头等信息。实现了发送响应的方法。

框架启动过程源码分析

接下来,我们来看框架的启动过程。我们知道,针对一个 web 应用,其入口文件是在 /public/index.php,我们来看这个文件具体内容。

//注册应用的自动加载器
require __DIR__.'/../bootstrap/autoload.php';

//创建 Application 类对象,做初始配置并返回
$app = require_once __DIR__.'/../bootstrap/app.php';

//创建针对 http 请求的 $kernel 对象
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

//在 $kernel 中执行请求 $request,并得到响应 $response
//$response 是 Illuminate\Http\Response 类的实例
$response = $kernel->handle(
    //根据 http 请求创建 $request 对象
    //其为 Illuminate\Http\Request 对象实例
    //管理 http 请求的query, server, input, file, cookie, session等信息
    $request = Illuminate\Http\Request::capture()
);

//发送响应
$response->send();

//终止针对请求 $request 得到响应 $response 的 $kernel
$kernel->terminate($request, $response);

我们看了应用入口文件的内容,了解了框架的大概启动流程,接下来我们来分析各个流程的具体内容。

框架的自动加载文件

在在入口文件中,我们看到,代码首先加载了框架的自动加载配置文件,我们来看其具体内容。


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

//加载由 composer 生成的框架各个包的自动加载规则和我们自定义的自动加载规则
//php 包的自动加载规则主要遵循 psr-4 标准
require __DIR__.'/../vendor/autoload.php';

$compiledPath = __DIR__.'/cache/compiled.php';

if (file_exists($compiledPath)) {
    require $compiledPath;
}

框架 Application 类对象的实例化及配置

当框架加载完自动加载文件后,开始生成核心类 Application 并做基本配置,我们来看具体代码。

//创建 Application 类对象
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

//添加绑定针对 http 请求的 Kernel 的绑定 
$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 的构造函数,看一下 Application 类对象的实例化过程

namespace Illuminate\Foundation;

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    /**
     * Create a new Illuminate application instance.
     * 
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        //在 Application 的容器里面添加基础的 binding
        //主要是 app 和 Illuminate\Container\Container
        $this->registerBaseBindings();

        //注册基础的 service provider
        //主要是启动事件监听服务和路由服务的 service provider 
        $this->registerBaseServiceProviders();

        //为框架里面的核心类注册别名
        $this->registerCoreContainerAliases();

        if ($basePath) {
            //设置应用的基础路径,并将基于基础路径得到的常用的其他路径绑定到容器
            $this->setBasePath($basePath);
        }
    }
}

Kernel 对象实例化及基本功能

在入口文件中,我们看到实例化 Application 类对象后,就开始了创建 Kernel 对象,创建请求的 $request 对象,并在 Kernel 对象中执行请求 $request,得到相应 $response。如下代码所示:

//创建针对 http 请求的 $kernel 对象
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

//在 $kernel 中执行请求 $request,并得到响应 $response
//$response 是 Illuminate\Http\Response 类的实例
$response = $kernel->handle(
    //根据 http 请求创建 $request 对象
    //其为 Illuminate\Http\Request 对象实例
    //管理 http 请求的query, server, input, file, cookie, session等信息
    $request = Illuminate\Http\Request::capture()
);

实例化 Kernel 对象

我们先来看 Kernel 对象的实例化过程

namespace Illuminate\Foundation\Http;

use Illuminate\Contracts\Http\Kernel as KernelContract;

class Kernel implements KernelContract{
    /**
     * The application implementation.
     * 
     * @var \Illuminate\Contracts\Foundation\Application
     */
    protected $app;

    /**
     * The router instance.
     *
     * @var \Illuminate\Routing\Router
     */
    protected $router;

    /**
     * The application's global HTTP middleware stack.
     * 运行 http 请求全局生效的中间件
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [];

    /**
     * The application's route middleware groups.
     * 应用中路由的中间件组
     * @var array
     */
    protected $middlewareGroups = [];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     * 路由中可能会用到的中间件的简化别名
     * @var array
     */
    protected $routeMiddleware = [];

    /**
     * The priority-sorted list of middleware.
     * Forces the listed middleware to always be in the given order.
     * 中间件的优先级,强制下面这些中间件按照给定的顺序执行
     * @var array
     */
    protected $middlewarePriority = [
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \Illuminate\Auth\Middleware\Authenticate::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Auth\Middleware\Authorize::class,
    ];


/**
     * Create a new HTTP kernel instance.
     * 
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @param  \Illuminate\Routing\Router  $router
     * @return void
     */
    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->middleware($key, $middleware);
        }
    }
}

我们看到在实例化 Kernel 对象过程中,主要设置了路由系统的中间件优先级、中间件别名和中间件组。

Kernel 对象执行请求过程

接下来我们来看在 $kernel 对象中执行请求 $request 并返回相应 $response 的过程。

namespace Illuminate\Foundation\Http;

use Illuminate\Contracts\Http\Kernel as KernelContract;

class Kernel implements KernelContract{
    /**
     * The application implementation.
     * 
     * @var \Illuminate\Contracts\Foundation\Application
     */
    protected $app;

    /**
     * The router instance.
     *
     * @var \Illuminate\Routing\Router
     */
    protected $router;

     /**
     * Handle an incoming HTTP request.
     * 执行一个 http 请求
     * @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']->fire('kernel.handled', [$request, $response]);

        return $response;
    }

     /**
     * Send the given request through the middleware / router.
     * 发送 $request,通过全局中间件,并分发给路由 
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendRequestThroughRouter($request)
    {
        //在 Application 类对象上绑定请求类 $request
        $this->app->instance('request', $request);

        Facade::clearResolvedInstance('request');

        //根据这个 http 请求启动应用,运行针对 http 请求的启动器
        $this->bootstrap();

        //将请求 $request 通过中间件,并分发给路由
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

     /**
     * Get the route dispatcher callback.
     * 返回在路由上分发请求的闭包
     * @return \Closure
     */
    protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance('request', $request);

            //根据路由分发请求
            return $this->router->dispatch($request);
        };
    }
}

我们看到将请求 $response 分发到路由并执行返回其响应调用了路由系统的相关方法,我们来看具体实现。

路由系统分发并执行请求

在 laravel 框架中,Illuminate\Routing\Router 类主要的作用是配置应用的路由,配置路由可能用到的中间件、中间件组,根据配置的路由表分配请求,根据分配到的路由运行请求得到相应。下面我们来看 Illuminate\Routing\Router 类中和框架启动相关的源码

namespace Illuminate\Routing;

class Router implements RegistrarContract
{
  /**
     * Dispatch the request to the application.
     * 分发请求到某个配置的路由
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    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)
    {
        //首先,在用户配置的路由组里面找到匹配请求的路由
        $route = $this->findRoute($request);

        $request->setRouteResolver(function () use ($route) {
            return $route;
        });

        //触发 RouteMatched 事件
        $this->events->fire(new Events\RouteMatched($route, $request));

        //让 $request 通过 $route 配置的中间件,得到运行路由的响应
        $response = $this->runRouteWithinStack($route, $request);

        //根据 $request 和 $response,准备 $response 响应对象
        return $this->prepareResponse($request, $response);
    }

       /**
     * 运行路由的中间件,让 $request 通过 $route 配置的中间件
     * @param  \Illuminate\Routing\Route  $route
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    protected function runRouteWithinStack(Route $route, Request $request)
    {
        //是否应该跳过中间件
        $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                                $this->container->make('middleware.disable') === true;

        $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

        //让请求 $request 通过 $route 的中间件,
        //并最终返回路由执行请求的响应
        return (new Pipeline($this->container))
                        ->send($request)
                        ->through($middleware)
                        ->then(function ($request) use ($route) {
                            return $this->prepareResponse(
                                $request, $route->run($request)
                            );
                        });
    }


  /**
     * Create a response instance from the given value.
     * 根据运行路由得到的响应配置框架的响应
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @param  mixed  $response
     * @return \Illuminate\Http\Response
     */
    public function prepareResponse($request, $response)
    {
        if ($response instanceof PsrResponseInterface) {
            $response = (new HttpFoundationFactory)->createResponse($response);
        } elseif (! $response instanceof SymfonyResponse) {
            $response = new Response($response);
        }

        return $response->prepare($request);
    }
}

laravel 框架路由系统中,类 Illuminate\Routing\Route 也是重要的核心类之一,在框架中,每配置一个请求的路由,都会生成一个 Illuminate\Routing\Route 类的实例,里面记录类请求的 uri、methods,以及对应的执行请求的控制器方法或者回调等信息。接下来我们来看类 Illuminate\Routing\Route 中和框架启动相关的内容。

namespace Illuminate\Routing;

class Route
{

   /**
     * Run the route action and return the response.
     * 运行路由配置的 action 并返回其响应
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    public function run(Request $request)
    {
        $this->container = $this->container ?: new Container;

        try {
            //如果路由是有控制器的方法执行
            //运行控制器对应的方法并返回
            if ($this->isControllerAction()) {
                return $this->runController();
            }

            //运行并返回路由对应的回调
            return $this->runCallable();
        } catch (HttpResponseException $e) {
            return $e->getResponse();
        }
    }
}

http应用的启动

在 Kernel 对象执行请求过程中,我们看到在将请求分发到路由之前,我们先启动了针对这个 http 请求的应用(通过语句 $this->bootstrap() 实现,$this 表示对象 $kernel )。接下来我们来看应用的启动过程。

namespace Illuminate\Foundation\Http;

use Illuminate\Contracts\Http\Kernel as KernelContract;

class Kernel implements KernelContract{
    /**
     * The application implementation.
     * 
     * @var \Illuminate\Contracts\Foundation\Application
     */
    protected $app;

    /**
     * The bootstrap classes for the application.
     *  启动一个 http 请求的应用所需的启动器的类
     * @var array
     */
    protected $bootstrappers = [
        'Illuminate\Foundation\Bootstrap\DetectEnvironment',
        'Illuminate\Foundation\Bootstrap\LoadConfiguration',
        'Illuminate\Foundation\Bootstrap\ConfigureLogging',
        'Illuminate\Foundation\Bootstrap\HandleExceptions',
        'Illuminate\Foundation\Bootstrap\RegisterFacades',
        'Illuminate\Foundation\Bootstrap\RegisterProviders',
        'Illuminate\Foundation\Bootstrap\BootProviders',
    ];

     /**
     * Bootstrap the application for HTTP requests.
     * 根据这个 http 请求启动应用,运行针对 http 请求的启动器
     * @return void
     */
    public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }

    /**
     * Get the bootstrap classes for the application.
     * 返回针对 http 请求的启动器类 
     * @return array
     */
    protected function bootstrappers()
    {
        return $this->bootstrappers;
    }
}

我看再来看 Application 类中启动一组启动器的相关代码

namespace Illuminate\Foundation;
class Application extends Container implements ApplicationContract, HttpKernelInterface
{

    /**
     * Run the given array of bootstrap classes.
     * 启动 $bootstrappers 数组里面的启动器
     * @param  array  $bootstrappers
     * @return void
     */
    public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;
        foreach ($bootstrappers as $bootstrapper) {
            //触发启动器 $bootstrapper 启动的监听事件
            $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
            $this->make($bootstrapper)->bootstrap($this);
            //触发启动器 $bootstrapper 启动后的监听事件
            $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
        }
    }
}

根据以上代码,我们知道所谓启动针对 http 请求的应用,也就是依次运行在 $kernel 对象中配置的启动器,每个启动器实现了启动应用过程中的某一块功能。

各个启动器具体功能如下:

  1. Illuminate\Foundation\Bootstrap\DetectEnvironment
    检测并配置环境。当框架没有缓存配置的时候,加载 .env 文件里面的配置信息

  2. Illuminate\Foundation\Bootstrap\LoadConfiguration
    加载框架的配置信息。当配置文件已经缓存,从配置缓存文件中加载配置信息,否则依次加载框架的配置文件里面的配置信息

  3. Illuminate\Foundation\Bootstrap\ConfigureLogging
    注册、配置并启动框架的日志系统

  4. Illuminate\Foundation\Bootstrap\HandleExceptions
    设置框架运行时的异常报错机制

  5. Illuminate\Foundation\Bootstrap\RegisterFacades
    注册框架的门面

  6. Illuminate\Foundation\Bootstrap\RegisterProviders
    注册框架的 service provider,也就是运行非延迟执行的 ServiceProvider 的 register 方法(执行 $app->registerConfiguredProviders() 语句实现)

  7. Illuminate\Foundation\Bootstrap\BootProviders
    启动框架的 service provider,也就是运行非延迟执行的 ServiceProvider 的 boot 方法(通过执行 $app->boot() 语句实现)

由于框架是依次执行各个启动器,所以我们知道了为什么 laravel 框架中, ServiceProvider 先执行 register 方法,后执行 boot 方法。

终止应用相关源码

在将请求分发到路由并执行得到相应之后,开始发送响应并在 Kernel 类对象中终止应用:

//发送响应内容
$response->send();
//终止针对请求 $request 得到响应 $response$kernel
$kernel->terminate($request, $response);

我们看到终止应用是通过 $kernel 对象调用 terminate 方法得到的,我们来看相应源码

namespace Illuminate\Foundation\Http;

use Illuminate\Contracts\Http\Kernel as KernelContract;

class Kernel implements KernelContract{
    /**
     * Call the terminate method on any terminable middleware.
     * 停止这个针对 http 请求的应用
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Http\Response  $response
     * @return void
     */
    public function terminate($request, $response)
    {
        //获取请求经过的中间件
        $middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
            $this->gatherRouteMiddleware($request),
            $this->middleware
        );
        //运行各个中间件的 terminate 方法
        foreach ($middlewares as $middleware) {
            if (! is_string($middleware)) {
                continue;
            }
            list($name, $parameters) = $this->parseMiddleware($middleware);
            $instance = $this->app->make($name);
            if (method_exists($instance, 'terminate')) {
                $instance->terminate($request, $response);
            }
        }
        //终止应用,应用 Application 对象的 terminate 方法
        $this->app->terminate();
    }
}

$kernel 终止的过程中,调用了 Application 类对象的 terminate 方法,我们来看一下其相关方法

namespace Illuminate\Foundation;
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
/**
     * Boot the application's service providers.
     * 启动这个 application 的 service provider
     * @return void
     */
    public function boot()
    {
        //如果应用已经启动,则直接返回
        if ($this->booted) {
            return;
        }
        //触发应用的启动的回调函数
        $this->fireAppCallbacks($this->bootingCallbacks);
        //启动框架中的设置的 service provider
        //array_walk 函数对数组中的每个元素应用用户自定义函数
        //在函数中,第一个参数是值,第二个参数是键。
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);
        });
        //设置应用已经启动
        $this->booted = true;
        //触发应用启动后的回调函数
        $this->fireAppCallbacks($this->bootedCallbacks);
    }
    /**
     * Terminate the application.
     * 终止这个应用
     * @return void
     */
    public function terminate()
    {
        //运行应用结束时的回调
        foreach ($this->terminatingCallbacks as $terminating) {
            $this->call($terminating);
        }
    }
}

通过以上的代码分析,我们可以看到 Application 类对象的 boot 方法是通过 Kernel 对象配置的启动器 Illuminate\Foundation\Bootstrap\BootProviders 来调用的,而 terminate 方法则是通过 Kernel 类对象的 terminate 方法来调用的。

总结

至此,我们大概了解了 laravel 框架的启动过程。了解框架的启动执行过程对于我们调试代码、理解框架设计思想具有很大帮助。
不过,框架的设计是一个整体性的东西,我们不仅要了解框架的启动过程,还要知道其他模块的运行原理,比如 Application 对 ServiceProvider 的管理,框架中间件的实现过程,Facade 的设计思想及实现,这些都是框架里面的核心概念和内容,理解这些东西,对于我们更好的理解和使用框架具有很大的帮助。大家可以看我相关的文章,通过这些文章的阅读,相信对理解框架启动过程和整体设计思想具有很大帮助。

你可能感兴趣的:(laravel,源码分析)