Lumen 源码阅读记录 ---- 初始化(1)

@liubb  October 23, 2018

入口:public/index.php

$app = require __DIR__.'/../bootstrap/app.php';
$app->run();

先看第一句:打开 app.php

<?php

require_once __DIR__.'/../vendor/autoload.php';

try {
    (new Dotenv\Dotenv(__DIR__.'/../'))->load();
} catch (Dotenv\Exception\InvalidPathException $e) {
    //
} 
$app = new Qudian\Base\Application(
    realpath(__DIR__.'/../')
);

$app->withAliases([
    Auto\Facades\Gateway::class => 'Gateway',
]);

$app->withFacades();

$app->configure('trade');

$app->withEloquent();

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

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

$app->routeMiddleware([
    'trace' => \Auto\Foundation\Http\Middlewares\TraceLogMiddleware::class,
]);

$app->register(App\Providers\EventServiceProvider::class);
$app->register(Auto\Providers\ServiceProvider::class);
$app->register(Laravel\Tinker\TinkerServiceProvider::class);
$app->register(Mpociot\ApiDoc\ApiDocGeneratorServiceProvider::class);
$app->register(Sentry\SentryLaravel\SentryLumenServiceProvider::class);
$app->register(Illuminate\Redis\RedisServiceProvider::class);
$app->register(Workflow\Business\Console\ConsoleServiceProvider::class);
$app->register(App\Providers\DBLogServiceProvider::class);

Queue::extend('aliyunmns', function () {
    return new Stone\Queue\Connectors\AliyunMNSConnector();
});

$app->router->group([
    'namespace' => 'App\Http\Controllers',
], function ($router) {
    require __DIR__.'/../routes/web.php';
});

return $app;

第一句,引入composer的自动加载文件,掠过。

require_once __DIR__.'/../vendor/autoload.php';

第二句,加载根目录下面的env文件,并设置成环境变量,不展开了,科普两个函数 file() putenv()

try {
    (new Dotenv\Dotenv(__DIR__.'/../'))->load();
} catch (Dotenv\Exception\InvalidPathException $e) {
    //
}

file() 函数把整个文件读入一个数组中。
与 file_get_contents() 类似,不同的是 file() 将文件作为一个数组返回。数组中的每个单元都是文件中相应的一行,包括换行符在内。
如果失败,则返回 false。

putenv() 设置环境变量的值, 例如 "FOO=BAR"

第三句,

$app = new \Laravel\Lumen\Application(
    realpath(__DIR__.'/../')
);

打开Application.php

public function __construct($basePath = null)
{
    if (! empty(env('APP_TIMEZONE'))) {
        date_default_timezone_set(env('APP_TIMEZONE', 'UTC'));
    }

    $this->basePath = $basePath;

    $this->bootstrapContainer();
    $this->registerErrorHandling();
    $this->bootstrapRouter();
}

一句句看下来。

$this->bootstrapContainer();

protected function bootstrapContainer()
{
    static::setInstance($this);

    $this->instance('app', $this);
    $this->instance('Laravel\Lumen\Application', $this);

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

    $this->registerContainerAliases();
}

先看 第一句,绑定自身。

static::setInstance($this);
public static function setInstance(ContainerContract $container = null)
{
    return static::$instance = $container;
}

二看 第二句

$this->instance('app', $this);
public function instance($abstract, $instance)
{
    $this->removeAbstractAlias($abstract);

    $isBound = $this->bound($abstract);

    unset($this->aliases[$abstract]);
    $this->instances[$abstract] = $instance;

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

    return $instance;
}

继续深入 查看removeAbstractAlias方法 尚未绑定 $this->aliases任何东西,直接返回。

$this->removeAbstractAlias($abstract);
protected function removeAbstractAlias($searched)
{
    if (! isset($this->aliases[$searched])) {
        return;
    }

    foreach ($this->abstractAliases as $abstract => $aliases) {
        foreach ($aliases as $index => $alias) {
            if ($alias == $searched) {
                unset($this->abstractAliases[$abstract][$index]);
            }
        }
    }
}

查看bound方法,返回false

$isBound = $this->bound($abstract);
public function bound($abstract)
{
    return isset($this->bindings[$abstract]) ||
           isset($this->instances[$abstract]) ||
           $this->isAlias($abstract);
}

接下来,将Instance的第一个参数和第二个参数做了个绑定

$this->instances[$abstract] = $instance;

第四句,因为$isBound=false,返回instance。

返回 bootstrapContainer()方法,看registerContainerAliases(),赋值了$this->aliases变量

回到Application.php 构造方法的第二个语句,设置了全局的错误捕捉函数,不展开了。

$this->registerErrorHandling();

构造方法的第三句,将Router绑定到 application上

$this->bootstrapRouter();
public function bootstrapRouter()
{
    $this->router = new Router($this);
}

到此为止,构造方法的函数全都讲解完毕。回到app.php文件内。

app.php 第四句

$app->withAliases([
    Auto\Facades\Gateway::class => 'Gateway',
]);
public function withAliases($userAliases = [])
{
    $defaults = [
        'Illuminate\Support\Facades\Auth' => 'Auth',
        'Illuminate\Support\Facades\Cache' => 'Cache',
        'Illuminate\Support\Facades\DB' => 'DB',
        'Illuminate\Support\Facades\Event' => 'Event',
        'Illuminate\Support\Facades\Gate' => 'Gate',
        'Illuminate\Support\Facades\Log' => 'Log',
        'Illuminate\Support\Facades\Queue' => 'Queue',
        'Illuminate\Support\Facades\Schema' => 'Schema',
        'Illuminate\Support\Facades\URL' => 'URL',
        'Illuminate\Support\Facades\Validator' => 'Validator',
    ];

    if (! static::$aliasesRegistered) {
        static::$aliasesRegistered = true;

        $merged = array_merge($defaults, $userAliases);

        foreach ($merged as $original => $alias) {
            class_alias($original, $alias);
        }
    }
}
设置类别名,如果没有阅读到此处的代码,会极大增加了使用框架时候的难度,会很难理解有些类从何而来。

bool class_alias ( string $original , string $alias [, bool $autoload = TRUE ] )
基于用户定义的类 original 创建别名 alias。 这个别名类和原有的类完全相同

app.php 第五句,把当前类 $this 传入 Facade,其他功能和第四句一样。

$app->withFacades();
public function withFacades($aliases = true, $userAliases = [])
{
    Facade::setFacadeApplication($this);

    if ($aliases) {
        $this->withAliases($userAliases);
    }
}

app.php 第六句

$app->configure('trade');
public function configure($name)
{
    if (isset($this->loadedConfigurations[$name])) {
        return;
    }

    $this->loadedConfigurations[$name] = true;

    $path = $this->getConfigurationPath($name);

    if ($path) {
        $this->make('config')->set($name, require $path);
    }
}

根据name获取config文件夹下对应的文件路径 不展开了 返回的path是 config/{$name}.php

$path = $this->getConfigurationPath($name);

这句深入展开

$this->make('config')->set($name, require $path);

先看make方法

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

    if (array_key_exists($abstract, $this->availableBindings) &&
        ! array_key_exists($this->availableBindings[$abstract], $this->ranServiceBinders)) {
        $this->{$method = $this->availableBindings[$abstract]}();

        $this->ranServiceBinders[$method] = true;
    }

    return parent::make($abstract, $parameters);
}

第一步获取aliases,前面有个 registerContainerAliases()方法注册了aliases,此处传入的是config,不存在,所以直接返回config

public function getAlias($abstract)
{
    if (! isset($this->aliases[$abstract])) {
        return $abstract;
    }

    if ($this->aliases[$abstract] === $abstract) {
        throw new LogicException("[{$abstract}] is aliased to itself.");
    }

    return $this->getAlias($this->aliases[$abstract]);
}

第二步,这里availableBindings是个预先写好的数组,返回的method是registerConfigBindings

$this->{$method = $this->availableBindings[$abstract]}();

接下来看到 registerConfigBindings()方法

protected function registerConfigBindings()
{
    $this->singleton('config', function () {
        return new ConfigRepository;
    });
}

展开 singleton()方法说下

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

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

    if (! $concrete instanceof Closure) {
        $concrete = $this->getClosure($abstract, $concrete);
    }

    $this->bindings[$abstract] = compact('concrete', 'shared');

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

看 dropStaleInstances()方法 清除掉绑定的instance aliases

protected function dropStaleInstances($abstract)
{
    unset($this->instances[$abstract], $this->aliases[$abstract]);
}

看 getClosure()方法,返回匿名函数

protected function getClosure($abstract, $concrete)
{
    return function ($container, $parameters = []) use ($abstract, $concrete) {
        if ($abstract == $concrete) {
            return $container->build($concrete);
        }

        return $container->make($concrete, $parameters);
    };
}

接下来 bindings'config' = 匿名函数 bindings'config' = $shared

$this->bindings[$abstract] = compact('concrete', 'shared');

这一句不做分析,此处没用到

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

回到make方法,parent::make() 重点

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 (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
        return $this->instances[$abstract];
    }

    $this->with[] = $parameters;

    $concrete = $this->getConcrete($abstract);
    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete);
    } else {
        $object = $this->make($concrete);
    }
    foreach ($this->getExtenders($abstract) as $extender) {
        $object = $extender($object, $this);
    }

    if ($this->isShared($abstract) && ! $needsContextualBuild) {
        $this->instances[$abstract] = $object;
    }

    $this->fireResolvingCallbacks($abstract, $object);
    $this->resolved[$abstract] = true;
    array_pop($this->with);
    return $object;
}

获取aliases别名 没有别名 返回的还是config

$abstract = $this->getAlias($abstract);

中间几句略过,没用到,看到这句,这里是获取在getClosure()方法时绑定的匿名函数

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

接下来,返回的是true

$this->isBuildable($concrete, $abstract)
protected function isBuildable($concrete, $abstract)
{
    return $concrete === $abstract || $concrete instanceof Closure;
}

那么就进入到

$object = $this->build($concrete);
public function build($concrete)
{
    // If the concrete type is actually a Closure, we will just execute it and
    // hand back the results of the functions, which allows functions to be
    // used as resolvers for more fine-tuned resolution of these objects.
    if ($concrete instanceof Closure) {
        return $concrete($this, $this->getLastParameterOverride());
    }

    $reflector = new ReflectionClass($concrete);

    // If the type is not instantiable, the developer is attempting to resolve
    // an abstract type such as an Interface of Abstract Class and there is
    // no binding registered for the abstractions so we need to bail out.
    if (! $reflector->isInstantiable()) {
        return $this->notInstantiable($concrete);
    }

    $this->buildStack[] = $concrete;

    $constructor = $reflector->getConstructor();

    // If there are no constructors, that means there are no dependencies then
    // we can just resolve the instances of the objects right away, without
    // resolving any other types or dependencies out of these containers.
    if (is_null($constructor)) {
        array_pop($this->buildStack);

        return new $concrete;
    }

    $dependencies = $constructor->getParameters();

    // Once we have all the constructor's parameters we can create each of the
    // dependency instances and then use the reflection instances to make a
    // new instance of this class, injecting the created dependencies in.
    $instances = $this->resolveDependencies(
        $dependencies
    );

    array_pop($this->buildStack);

    return $reflector->newInstanceArgs($instances);
}

因为concrete是匿名函数

if ($concrete instanceof Closure) {
    return $concrete($this, $this->getLastParameterOverride());
}

//也就是此处返回的是
function () {
    return new ConfigRepository;
});

回到Application.php $this->make('config') 返回的是 ConfigRepository

$this->make('config')->set($name, require $path);
为 ConfigRepository 里面的 $this->items 赋值
一开始有个疑问,如果每次都make,items也不是静态变量,为什么可以一直留存在内存中。
后面发现,application的instances变量,如果已经存在,就不会重新走一遍流程
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
    return $this->instances[$abstract];
}

回到 app.php,上面的make已经讲过了。

$app->withEloquent();
public function withEloquent()
{
    $this->make('db');
}

接下来,singleton上面已经讲过了

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

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

接下来,中间件,下次讲解

$app->routeMiddleware([
    'trace' => \Auto\Foundation\Http\Middlewares\TraceLogMiddleware::class,
]);

接下来,注册,没什么难度,略过

$app->register(App\Providers\EventServiceProvider::class);
public function register($provider)
{
    if (! $provider instanceof ServiceProvider) {
        $provider = new $provider($this);
    }

    if (array_key_exists($providerName = get_class($provider), $this->loadedProviders)) {
        return;
    }

    $this->loadedProviders[$providerName] = true;

    if (method_exists($provider, 'register')) {
        $provider->register();
    }

    if (method_exists($provider, 'boot')) {
        return $this->call([$provider, 'boot']);
    }
}

接下来,此处的Queue很难理解从哪里引入,其实就是上面的class_alia函数,设置了别名。

Queue::extend('aliyunmns', function () {
    return new Stone\Queue\Connectors\AliyunMNSConnector();
});

接下来,这里的route 是 Application.php 构造方法里的 $this->bootstrapRouter() 函数赋值的,路由的时候展开讲解。

$app->router->group([
    'namespace' => 'App\Http\Controllers',
], function ($router) {
    require __DIR__.'/../routes/web.php';
});

至此,app.php的初始化完成。
回到index.php,第二篇开始讲解路由

$app->run();

添加新评论