Laravel 启动流程分析 (代码全流程)

转载地址:https://learnku.com/articles/19878

入口文件 index.php中,引入核心启动文件

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

app.php做了什么事情?

// 实例化应用 $app
$app = new Illuminate\Foundation\Application(
    dirname(__DIR__)
);

接下来全程跟踪 $app 中属性的变化

看下 Application 中的构造方法,逐行通过注释分析

// 字面理解
if ($basePath) {
    // 设置基础路径信息
    $this->setBasePath($basePath);
}
// 注册基础绑定
$this->registerBaseBindings();
// 注册基础服务容器
$this->registerBaseServiceProviders();
// 注册核心容器别名
$this->registerCoreContainerAliases();

快速看代码 setBasePath 方法代码

*   *   *   *
$this->bindPathsInContainer();
*   *   *   *

快速看 bindPathsInContainer 代码

// 绑定非自动加载路径到容器
*   *   *   *
$this->instance('path', $this->path());
*   *   *   *
$app->instances = [
    'path'  => 'xxx'
]

认真看 registerBaseBindings 代码

// 绑定 Application 实例到 @todo Container::$instance
static::setInstance($this);
// 绑定 Application 实例到 $app->instances['app']
$this->instance('app', $this);
// 绑定 Application 实例到 $app->instances['Container'] 数组
$this->instance(Container::class, $this);

// 绑定 PackageManifest 实例到 $app->instances['PackageManifest'] 数组
$this->instance(PackageManifest::class, new PackageManifest(
    new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));

*   *   *   *
$app->instances['app'] = $this
$app->instances['Container'] = $this
$app->instances['PackageManifest'] = new PackageManifest(xxx)
注意 $this 是 Application 实例

仔细看 registerBaseServiceProviders 代码

// 只举例分析第一条,一下两行代码类似
$this->register(new EventServiceProvider($this));
// 注册日志容器
$this->register(new LogServiceProvider($this));
// 注册路由容器
$this->register(new RoutingServiceProvider($this));

先看参数,每个resister的参数都是一个 ServiceProvider 实例,简单看一下 EventServiceProvider 类 和 其继承的 ServiceProvider 抽象类


abstract class ServiceProvider
{
    *   *   *   *
    protected $app;

    public function __construct($app)
    {
        // 将 Application 实例传给 app 属性
        $this->app = $app;
    }
    *   *   *   *
}

关键源码分析 敲黑板 关键

*   *   *   *
register源码
public function register($provider, $force = false)
{
    // 检查容器是否注册过,如果已经注册过,直接返回容器
    if (($registered = $this->getProvider($provider)) && ! $force) {
        return $registered;
    }
    // 如果传入的是字符串,则通过字符串解析容器
    if (is_string($provider)) {
        $provider = $this->resolveProvider($provider);
    }

    // 由于传入的是一个Provider实例,并且含有 register 方法
    if (method_exists($provider, 'register')) {
        $provider->register();
    }
    // 如果传入的是Provider实例,并且含有 bindings 方法
    if (property_exists($provider, 'bindings')) {
        foreach ($provider->bindings as $key => $value) {
            $this->bind($key, $value);
        }
    }

    // 如果传入的是Provider实例,并且含有 singletons 方法
    if (property_exists($provider, 'singletons')) {
        foreach ($provider->singletons as $key => $value) {
            $this->singleton($key, $value);
        }
    }

    // 将Provider打上已经注册的标识
    $this->markAsRegistered($provider);

    // 如果应用已经启动,则调用Provider实例的 boot 方法
    if ($this->booted) {
        $this->bootProvider($provider);
    }

    return $provider;
}

关于服务容器中 register,bindings,singletons,boot方法的使用场景,可以查阅 这里

这里举例看EventServiceProvider的register方法查看

class EventServiceProvider extends ServiceProvider
{
    public function register()
    {
        // 调用了 Application 实例的 singleton 方法
        $this->app->singleton('events', function ($app) {
            return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                return $app->make(QueueFactoryContract::class);
            });
        });
    }
}
*   *   *   *
查看 Application 实例的 singleton 方法

public function singleton($abstract, $concrete = null)
{
    $this->bind($abstract, $concrete, true);
}

查看 Application 实例的 bind 方法

public function bind($abstract, $concrete = null, $shared = false)
{
    $this->dropStaleInstances($abstract);

    if (is_null($concrete)) {
        $concrete = $abstract;
    }

    // 传入的 concrete 是一个回调函数
    if (! $concrete instanceof Closure) {   // false
        $concrete = $this->getClosure($abstract, $concrete);
    }

    // $this->bindings['events'] = [concrete (这是前面传入的闭包函数), true (是否共享,也就是是否单例)];
    $this->bindings[$abstract] = compact('concrete', 'shared');

    if ($this->resolved($abstract)) {
        $this->rebound($abstract);
    }
}

到此,singleton执行完毕,我们可以看到最终的结果,是把一个闭包函数

function ($app) {
    return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
        return $app->make(QueueFactoryContract::class);
    });
}

和 key events以键值对的方式传入了 $app 下的 bindings 属性

$app->bindings['events'] = [
    function ($app) {
        return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
            return $app->make(QueueFactoryContract::class);
        });
    },
    true
]

由此,我们在回头看一下我们的 Application 类的属性组成 tips: 主要是 Container 基类

    /**
     * The current globally available container (if any).
     * 当前全局可用容器
     * @var static
     */
    protected static $instance;

    /**
     * An array of the types that have been resolved.
     * 一个已经被解析的类型数组
     * @var array
     */
    protected $resolved = [];

    /**
     * The container's bindings.
     * 容器的绑定数组
     * @var array
     */
    protected $bindings = [];

    /**
     * The container's method bindings.
     * 容器的方法绑定数组
     * @var array
     */
    protected $methodBindings = [];

    /**
     * The container's shared instances.
     * 容器的可被分享的实例数组
     * @var array
     */
    protected $instances = [];

我们经过上面的分析,我们可以得到如下结果

$app->instances = [x,x,x]
$app->bindings = [x,x,x]
$app->resolved = [x,x,x]

我们是不是可以得出一个大概的推论,Laravel的启动过程,其实是以上这些属性填满的过程,
换句话说,这些属性数组保存了laravel启动过程中基本所有需要使用到的东西 :blush:

OK,回到 Application 的构造方法继续分析,剩下

$this->registerCoreContainerAliases();
*   *   *   *
foreach ([
    'app'                  => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class],
    .   .   .   .
] as $key => $aliases) {
    foreach ($aliases as $alias) {
        // 将核心容器起别名放入 $app->alias 属性中
        $this->alias($key, $alias);
    }
}
*   *   *   *
$app->alias = [
    'app'   =>  [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class]
    .   .   .   .
]

$app->alias被赋值是不是从侧面也印证了上面的推论 :)


经过上面一大串子分析,Laravel已经加载了大部分的基础服务,现在是不是还有个疑问,我们的请求是怎么到Controller的呢
答案就在app.php的下面的几行代码了

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

OK,我们在看一遍 singleton 的源码 (精简版的 :blush: )

// 传入的 ¥concrete 是 App\Http\Kernel::class 是一串字符串,并不是闭包
if (! $concrete instanceof Closure) {
    // 执行 getClosure 函数
    $concrete = $this->getClosure($abstract, $concrete);
}
// 老样子,放到 bindings 数组中,作为单例存在
$this->bindings[$abstract] = compact('concrete', 'shared');

*   *   *   *
//  接着看 getClosure
// 函数返回了一个闭包
protected function getClosure($abstract, $concrete) 
{
    return function ($container, $parameters = []) use ($abstract, $concrete) {
        // 这里 $abstract != $concrete
        if ($abstract == $concrete) {
            return $container->build($concrete);
        }
        // 执行 Application 实例的make方法,传入 App\Http\Kernel::class, []
        return $container->make($concrete, $parameters);
    };
}

*   *   *   *
// 接着看 Application实例的make方法
public function make($abstract, array $parameters = [])
{
    // mmp 调用了 resolve
    return $this->resolve($abstract, $parameters);
}

*   *   *   *
// 传入 App\Http\Kernel::class 和 []
protected function resolve($abstract, $parameters = [])
{
    // 没有别名
    $abstract = $this->getAlias($abstract);

    $needsContextualBuild = ! empty($parameters) || ! is_null(
        $this->getContextualConcrete($abstract)
    );

    // 没有实例化过
    if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
        return $this->instances[$abstract];
    }

    $this->with[] = $parameters;

    $concrete = $this->getConcrete($abstract);

    // $concrete === $abstract 为 true
    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;
}

*   *   *   *
// 查看build方法,快结束了,撑住

public function build($concrete)
{
    // 如果是闭包,执行闭包
    if ($concrete instanceof Closure) {
        return $concrete($this, $this->getLastParameterOverride());
    }
    // 实例化反射类,传入 App\Http\Kernel::class
    $reflector = new ReflectionClass($concrete);

    // 如果不能被实例化,报错
    if (! $reflector->isInstantiable()) {
        return $this->notInstantiable($concrete);
    }
    // buildStack是一个需要被实例化的类组成的栈
    $this->buildStack[] = $concrete;
    // 获取构造方法
    $constructor = $reflector->getConstructor();

    // 如果没有构造方法,则直接实例化这个类
    if (is_null($constructor)) {
        array_pop($this->buildStack);

        return new $concrete;
    }
    // 否则就获取构造方法的参数,参数即为该类的依赖
    $dependencies = $constructor->getParameters();

    // 递归解析并实例化依赖
    $instances = $this->resolveDependencies(
        $dependencies
    );
    // 弹出栈 note: 注意递归的情况
    array_pop($this->buildStack);
    // 传入依赖并实例化类 note:注意递归
    return $reflector->newInstanceArgs($instances);
}

通过上面的code,我们已经实例化的 App\Http\Kernel::class 类,并且已经解决了类的依赖问题
需要特别注意的一点是,该类在的构造函数中还做了一些别的操作,比如中间件的初始化等操作。
还是贴代码看一下吧

// App\Http\Kernel::class 类依赖 Application 和 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);
    }
}

Ok,HttpKernel部分分析完成。
以下两段代码分析与上述过程一样,不在赘述

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

再回到梦开始的地方 index.php,接着分析

// 通过Application的make方法,可以直接拿出Kernel实例,因为之前已经做过单例了
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

// 调用 Kernel 实例的 handle 方法
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

*   *   *   *
// 接着看 Illuminate\Http\Request::capture() 静态方法
// 这里只贴往下追后的代码了昂
public static function createFromBase(SymfonyRequest $request)
{
    if ($request instanceof static) {
        return $request;
    }

    $content = $request->content;

    $request = (new static)->duplicate(
        $request->query->all(), $request->request->all(), $request->attributes->all(),
        $request->cookies->all(), $request->files->all(), $request->server->all()
    );

    $request->content = $content;

    $request->request = $request->getInputSource();

    return $request;
}

// 以上这段代码的核心就是构造了一个 Request 对象,没什么特别的

*   *   *   *
// 接着看 handle 方法,只贴一行代码
$response = $this->sendRequestThroughRouter($request);

// 哇塞,好激动有没有,看到这行代码的我差点哭出来,请求和路由有关系了
// 继续往下看 sendRequestThroughRouter 方法

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    // 看这里,dispatchToRouter 将请求分发到路由上哎
    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

*   *   *   *

// 继续往下坠 dispatchToRouter ,会有一个方法

protected function findRoute($request)
{
    // 这里的routes是一个 RouteCollection 类,通过match方法,将请求解析到具体的路由上
    // RouteCollection是什么时候创建的???兄弟,还记得之前的 RoutingServiceProvider吗 :),在注册的时候都已经跑完了
    $this->current = $route = $this->routes->match($request);

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

    return $route;
}

至此,O了个K,Laravel的启动流程结束。

猜你喜欢

转载自blog.csdn.net/lengyue1084/article/details/88314425