我独自走进 Laravel5.5 的❤(六)

服务提供者是怎么回事?一起来看看吧。
2018-11-09
一、用法
首先,看个小例子。
因为用法网上有很多例子,这里就简单点。
在项目下
php artisan make:provider FunnyServiceProvider
编辑 App\Providers\FunnyServiceProvider.php

public function register()
    {
        //字符串:别称,闭包:注册的代码
        $this->app->singleton('funny', function ($app) {
             return new Funny();
        });

        $this->app->bind('funny', function ($app) {
            return new Funny();
        });

        $obj = new Funny();
        $this->app->instance('funny', $obj);
//      上面三个都一个意思
//        语境绑定(context)
//        $this->app->when()->needs()->give();
    }

然后在类中使用

class PublicController extends Controller
{
    public function hello()
    {
        $obj = app('funny');
        $res = $obj->fun();
        return $res;
    }
}

二、源码解析(看看服务在哪里注入的)

1.当http请求进入框架

//生成一个 Illuminate\Foundation\Application 实例

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

//使用实例处理请求

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
  1. 关于Illuminate\Foundation\Http\Kernel::class->handle()
public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();//这个看下面的2.1

        $response = $this->sendRequestThroughRouter($request);//这个看下面的2.2
    } 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;
}

2.1 关于enableHttpMethodParameterOverride()

//这个静态函数会在处理post请求的时候将请求变量覆盖的flag设置为true,可以设置delete或者put这些方法

public static function enableHttpMethodParameterOverride()
{
    self::$httpMethodParameterOverride = true;
}

//为了加深理解,我们可以看一下Symfony\Component\HttpFoundation\Request.php

//可以发现

protected static $httpMethodParameterOverride = false;

//这是一个初始化值为false的属性

//再看Request处理请求方法的函数

public function getMethod()
{
    if (null === $this->method) {
        $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));

        if ('POST' === $this->method) {
            if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
                $this->method = strtoupper($method);
            } elseif (self::$httpMethodParameterOverride) {
                $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST')));
            }
        }
    }

    return $this->method;
}

//通过getMethod(),相信你看出了个一二来了,如果httpMethodParameterOverride为真,会将_method覆盖post请求方法。

2.2关于sendRequestThroughRouter()

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

    $this->bootstrap();//这个看下面的2.2.1

    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

//1.无论按源码来看还是按处理逻辑来看或者是从函数名字来看,这个函数都理应起到一个过滤request的作用。这一点,从return可以看出,这个函数会将request倒进middleware池里,被路由注册的中间件轮过之后再返回。

//2.然鹅,其实那句看似平凡的$this->bootstrap()才这个函数的精华所在。

2.2.1关于bootstrap()

public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());//这个看下面的2.2.2
    }
}

//这是什么?如下:

array (size=6)
  0 => string 'Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables' (length=56)
  1 => string 'Illuminate\Foundation\Bootstrap\LoadConfiguration' (length=49)
  2 => string 'Illuminate\Foundation\Bootstrap\HandleExceptions' (length=48)
  3 => string 'Illuminate\Foundation\Bootstrap\RegisterFacades' (length=47)
  4 => string 'Illuminate\Foundation\Bootstrap\RegisterProviders' (length=49)
  5 => string 'Illuminate\Foundation\Bootstrap\BootProviders' (length=45)

//内心颤抖了一下,这几位难不成就是女娲、夸父、孙行者、玉皇大帝、菩萨,和如来吗?

//这里按道理便是在app中引入各种环境变量、配置信息、异常处理机制、注册门面、绑定服务、启动服务?

//不急,接着看。

我在 FunnyServiceProvider中十分灵性地打了一个断点,然后在$this->app->bootstrapWith($this->bootstrappers());下面一行也打了个断点,你猜怎么着。

图一:

图二:

结果它会先跑到图一,然后再跑到图二。所以,很明显,在 bootsrap() 中会注册所有的服务。也就是说,在中间件轮 request 之前,lara 就会注册所有的服务。

2.2.2 关于Illuminate\Foundation\Application::class->bootstrapWith()

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

// ok ,我们来看一下保时捷的发动机。

// $this['events'] 这是啥玩意?

//这个就是 Illuminate\Events\Dispatcher::class

//这个类可以通过其 fire() 方法来启动容器实例中的所有服务,什么监听器啊,事件啊,队列解析器啊等等与容器相关联的功能。总的来说,就是 lara 跑起来了。
//再来看一下 fire() 本真

public function fire($event, $payload = [], $halt = false)
{
    return $this->dispatch($event, $payload, $halt);
}

//同志们,dispatch()应该是老相识了,只不过知人知面不知心

//ok ,再来看看dispatch()是个什么鬼

public function dispatch($event, $payload = [], $halt = false)
{
    // When the given "event" is actually an object we will assume it is an event
    // object and use the class as the event name and this event itself as the
    // payload to the handler, which makes object based events quite simple.

   //当给定的“事件”实际上是一个对象时,我们将假设它是一个事件对象
   //并使用该类作为事件名称,并将此事件本身作为
   //处理器的有效负载,使对象基于事件非常简单。
    list($event, $payload) = $this->parseEventAndPayload(
        $event, $payload
    );

    if ($this->shouldBroadcast($payload)) {
        $this->broadcastEvent($payload[0]);
    }

    $responses = [];

    foreach ($this->getListeners($event) as $listener) {
        $response = $listener($event, $payload);

        // If a response is returned from the listener and event halting is enabled
        // we will just return this response, and not call the rest of the event
        // listeners. Otherwise we will add the response on the response list.

        //如果监听器返回响应,并启用了事件暂停($halt==true)
        //我们将返回此响应,并且不会调用剩下的事件监听器
        //否则,我们将在响应列表中添加响应。

        if ($halt && ! is_null($response)) {
            return $response;
        }

        // If a boolean false is returned from a listener, we will stop propagating
        // the event to any further listeners down in the chain, else we keep on
        // looping through the listeners and firing every one in our sequence.
       //如果从监听器返回布尔值false,我们将停止传播
       //事件发送到链中的任何其他监听器,否则我们继续
       //循环遍历侦听器并按顺序触发每个侦听器。

        if ($response === false) {
            break;
        }

        $responses[] = $response;
    }

    return $halt ? null : $responses;
}

2018-11-12
因为最近项目做完了,所以今天还是有时间写文章的,嘻嘻,那就接着上星期的来吧。
3.1

虽然在我们严谨的推理下,我们知道了lara的服务按理应该是在Illuminate\Foundation\Application::class->bootstrapWith()这个函数里面被正式注册到实例的。
但是,我们自己注册的服务被哪个类所控制的呢?
在 2.2.1 中其实有一个是答案
我胡乱一猜,应该是 Illuminate\Foundation\Bootstrap\RegisterProviders 吧,按字面意思嘛。
但是,我们知道,我们作为求知者是不能带有强烈的主观意识的。于是我客观地打了几个断点,顺便看了一下被分发的事件的负载是啥。

图三:

果不其然,当 lara 跑到

debug_dispacher_196>>>>>>>>evnt>>>>>>>> 
B:\projects\web\php\hello_laravel_2\vendor\laravel\framework\src\Illuminate\Events\Dispatcher.php:193:string 'bootstrapping: Illuminate\Foundation\Bootstrap\RegisterProviders' (length=64)

lara 会跳到 2.2.1 中的的图一,我们在 2.2.1 已经知道 lara 是是循环了几个框架控制类。所以,当 lara 注册到
Illuminate\Foundation\Bootstrap\RegisterProvider.php 时会注册自定义 provider。

为什么?

如果在高中课堂上,老师把一个知识点讲得比较深入,我还去问一个细节问题的为什么,我便会被老师和同学笑话。以前觉得愤愤不平,现在想来的确挺可笑的。再深入,应当自己去探索与思索,而不是再去靠别人告诉你了。
那么好的,我们继续看一下为啥子。
打开 Illuminate\Foundation\Bootstrap\RegisterProvider.php 我们可以看到

public function bootstrap(Application $app)
{
    $app->registerConfiguredProviders();
}

索性 control B 一下 registerConfiguredProviders,我们可以发现它是非常核心的一个接口类 Application 声明的方法,自然它的实现应当在
Illumidation\Foundation\Application:class 中被实现。于是,我满怀自信地打开了 Illumidation\Foundation\Application.php,果不其然。

/**
* Register all of the configured providers.
*
* @return void
*/

/**
* 注册绑定所有的设定的服务.
*在英文中,configured 一般指的是可以被使用配置的,也就是说可以被使用者自行的配置的(自定义的),也就是非官方的服务。
* @return void
*/

public function registerConfiguredProviders()
{
    $providers = Collection::make($this->config['app.providers'])
                    ->partition(function ($provider) {
                        return Str::startsWith($provider, 'Illuminate\\');//为啥两斜杠? 是转义字符哦
                    });

    $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

    (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                ->load($providers->collapse()->toArray());
}

这个明显是个操作函数,于是我打印了一下 $this->config['app.providers'],这个的确是我们想象中的 config/app.php 中的 providers,突然感觉自己好聪明?不信?有图有真相

打印结果如下:

B:\projects\web\php\hello_laravel_2\vendor\laravel\framework\src\Illuminate\Foundation\Application.php:558:
array (size=27)
  0 => string 'Illuminate\Auth\AuthServiceProvider' (length=35)
  1 => string 'Illuminate\Broadcasting\BroadcastServiceProvider' (length=48)
  2 => string 'Illuminate\Bus\BusServiceProvider' (length=33)
  3 => string 'Illuminate\Cache\CacheServiceProvider' (length=37)
  4 => string 'Illuminate\Foundation\Providers\ConsoleSupportServiceProvider' (length=61)
  5 => string 'Illuminate\Cookie\CookieServiceProvider' (length=39)
  6 => string 'Illuminate\Database\DatabaseServiceProvider' (length=43)
  7 => string 'Illuminate\Encryption\EncryptionServiceProvider' (length=47)
  8 => string 'Illuminate\Filesystem\FilesystemServiceProvider' (length=47)
  9 => string 'Illuminate\Foundation\Providers\FoundationServiceProvider' (length=57)
  10 => string 'Illuminate\Hashing\HashServiceProvider' (length=38)
  11 => string 'Illuminate\Mail\MailServiceProvider' (length=35)
  12 => string 'Illuminate\Notifications\NotificationServiceProvider' (length=52)
  13 => string 'Illuminate\Pagination\PaginationServiceProvider' (length=47)
  14 => string 'Illuminate\Pipeline\PipelineServiceProvider' (length=43)
  15 => string 'Illuminate\Queue\QueueServiceProvider' (length=37)
  16 => string 'Illuminate\Redis\RedisServiceProvider' (length=37)
  17 => string 'Illuminate\Auth\Passwords\PasswordResetServiceProvider' (length=54)
  18 => string 'Illuminate\Session\SessionServiceProvider' (length=41)
  19 => string 'Illuminate\Translation\TranslationServiceProvider' (length=49)
  20 => string 'Illuminate\Validation\ValidationServiceProvider' (length=47)
  21 => string 'Illuminate\View\ViewServiceProvider' (length=35)
  22 => string 'App\Providers\AppServiceProvider' (length=32)
  23 => string 'App\Providers\AuthServiceProvider' (length=33)
  24 => string 'App\Providers\EventServiceProvider' (length=34)
  25 => string 'App\Providers\RouteServiceProvider' (length=34)
  26 => string 'App\Providers\FunnyServiceProvider' (length=34)
>>>>>>>>>>>>>> providers_1 >>>>>>>>>>>>>>
B:\projects\web\php\hello_laravel_2\vendor\laravel\framework\src\Illuminate\Foundation\Application.php:560:
object(Illuminate\Support\Collection)[21]
  protected 'items' => 
    array (size=22)
      0 => string 'Illuminate\Auth\AuthServiceProvider' (length=35)
      1 => string 'Illuminate\Broadcasting\BroadcastServiceProvider' (length=48)
      2 => string 'Illuminate\Bus\BusServiceProvider' (length=33)
      3 => string 'Illuminate\Cache\CacheServiceProvider' (length=37)
      4 => string 'Illuminate\Foundation\Providers\ConsoleSupportServiceProvider' (length=61)
      5 => string 'Illuminate\Cookie\CookieServiceProvider' (length=39)
      6 => string 'Illuminate\Database\DatabaseServiceProvider' (length=43)
      7 => string 'Illuminate\Encryption\EncryptionServiceProvider' (length=47)
      8 => string 'Illuminate\Filesystem\FilesystemServiceProvider' (length=47)
      9 => string 'Illuminate\Foundation\Providers\FoundationServiceProvider' (length=57)
      10 => string 'Illuminate\Hashing\HashServiceProvider' (length=38)
      11 => string 'Illuminate\Mail\MailServiceProvider' (length=35)
      12 => string 'Illuminate\Notifications\NotificationServiceProvider' (length=52)
      13 => string 'Illuminate\Pagination\PaginationServiceProvider' (length=47)
      14 => string 'Illuminate\Pipeline\PipelineServiceProvider' (length=43)
      15 => string 'Illuminate\Queue\QueueServiceProvider' (length=37)
      16 => string 'Illuminate\Redis\RedisServiceProvider' (length=37)
      17 => string 'Illuminate\Auth\Passwords\PasswordResetServiceProvider' (length=54)
      18 => string 'Illuminate\Session\SessionServiceProvider' (length=41)
      19 => string 'Illuminate\Translation\TranslationServiceProvider' (length=49)
      20 => string 'Illuminate\Validation\ValidationServiceProvider' (length=47)
      21 => string 'Illuminate\View\ViewServiceProvider' (length=35)
>>>>>>>>>>>>>> providers_2 >>>>>>>>>>>>>>
B:\projects\web\php\hello_laravel_2\vendor\laravel\framework\src\Illuminate\Foundation\Application.php:562:
object(Illuminate\Support\Collection)[31]
  protected 'items' => 
    array (size=5)
      22 => string 'App\Providers\AppServiceProvider' (length=32)
      23 => string 'App\Providers\AuthServiceProvider' (length=33)
      24 => string 'App\Providers\EventServiceProvider' (length=34)
      25 => string 'App\Providers\RouteServiceProvider' (length=34)
      26 => string 'App\Providers\FunnyServiceProvider' (length=34)
>>>>>>>>>>>> debug_Application_559 >>>>>>>>>>>>

我们看到 providers_2,FunnyServiceProvider 赫然纸上。是不是觉得很神奇,还学会了新技能


Collection::make($arr = [])
                ->partition(function ($item) {
                    return Str::startsWith($item, $str);
                });
//顺便,我们来学习一下 lara 优秀的代码结构,看一下 partition()

public function partition($callback)
{
    $partitions = [new static, new static];//static: 传入的对象

    $callback = $this->valueRetriever($callback);//这个看下面的3.1.1

    foreach ($this->items as $key => $item) {
        $partitions[(int) ! $callback($item, $key)][$key] = $item;

        //如果闭包条件判断 item 返回 false 的放到 offset 为 0 子数组,否则放 1 中。
    }

    return new static($partitions);
}
//我们可以看到,在 lara 中也是随处可见 foreach,但是仔细理性分析一下,我们似乎难以寻找到内嵌的 foreach。所以为啥 lara 每个请求都要跑那么多行代码却速度飞快?代码中蕴含的逻辑性、功能性铸就了强大的 lara。

通过 partition() 中的闭包可以将 $arr 分割成两个特征不同的数组。

3.1.1关于valueRetriever()

protected function valueRetriever($value)
{
    if ($this->useAsCallable($value)) {
        return $value;
    }

    return function ($item) use ($value) {
        return data_get($item, $value);
    };
}

//这又是啥玩意?
//两底层函数

protected function useAsCallable($value)
{
    return ! is_string($value) && is_callable($value);
    //判断 value 是否不为字符串并且可以被被当作函数调用,就是判断是不是个闭包?
}

function data_get($target, $key, $default = null)
{
    if (is_null($key)) {
        return $target;
    }

    $key = is_array($key) ? $key : explode('.', $key);                         //判断 key 是否为数组,并根据"."分割数组

    while (! is_null($segment = array_shift($key))) {                        //将第一个子数组扔了(在"."左边存在值时)
        if ($segment === '*') {                                                          //如果key 形如 "*.abc"
            if ($target instanceof Collection) {                                    //如果对象为集合
                $target = $target->all(); 
            } elseif (! is_array($target)) {                                              //如果对象为数组
                return value($default);
            }

            $result = Arr::pluck($target, $key);                                  //从对象中取出相对应该的值

            return in_array('*', $key) ? Arr::collapse($result) : $result;
        }

        if (Arr::accessible($target) && Arr::exists($target, $segment)) {
            $target = $target[$segment];
        } elseif (is_object($target) && isset($target->{$segment})) {
            $target = $target->{$segment};
        } else {
            return value($default);
        }
    }

    return $target;
}
//这个不结合上下文,是真的很难理解。(对我而言。。。)
//来看个例子

foreach ((array) data_get($composer, 'autoload.psr-4') as $namespace => $path) {
    foreach ((array) $path as $pathChoice) {
        if (realpath(app_path()) == realpath(base_path().'/'.$pathChoice)) {
            return $this->namespace = $namespace;
        }
    }
}
//所以这是个根据”点“作为标记从数组或对象中获取元素的方法。

3.2 加载
当然,在3.1中只是将需要加载的 providers 列出来而已。
真正的加载其实是

(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
            ->load($providers->collapse()->toArray());

<--------------------- End ---------------------->
以上便是ioc启动服务提供者涉及到的流程,如果有啥不对,请指出,互相学习。

至此,

致敬知识。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 11
wanghan

你这个头像我能看一天

5年前 评论
LeoYao

@wanghan :joy:兄dei,佛系的吧

5年前 评论
falling-ts

我强迫出来了,为什么这个头像加载不上来,我要薅头发。。。。。。

5年前 评论

看这些底层好累

5年前 评论
LeoYao

@yuanshang 不怕秃了吗?

5年前 评论
LeoYao

@万能付博 不是挺好玩的吗,嘻嘻

5年前 评论
Echos

写的不错 继续哦

5年前 评论
LeoYao

@Echos hoho,好滴

5年前 评论

@Wiki_Yao 兄弟,能换个头像不,脑袋晕

5年前 评论
LeoYao

@xiaobei 兄dei 你可以别关注我头像不

5年前 评论

今天看了facades感觉挺棒的。

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!