Laravel ServiceProvider注册过程及简单使用

Laravel ServiceProvider注册过程及简单使用

还记得facade注册流程吗?回顾下

在bootstrap/app.php中返回$app实例后,通过singleton方法绑定了三个实现,然后将$app返回给了index.php,在index.php中尝试解析了http kernel,并且调用了kernel的handle方法(传递了请求实例),将通过handle方法返回的reponse返回给客户端,其中facade就是在处理请求的过程中注册的,同样的serviceprovider注册就在facade注册后面。

还记得这个数组吗?

protected $bootstrappers = [

​ \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,

​ \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,

​ \Illuminate\Foundation\Bootstrap\HandleExceptions::class,

​ \Illuminate\Foundation\Bootstrap\RegisterFacades::class,

​ \Illuminate\Foundation\Bootstrap\RegisterProviders::class,

​ \Illuminate\Foundation\Bootstrap\BootProviders::class,

];

上篇讲解了RegisterFacades类

此文只讲解RegisterProviders类,至于BootProviders就留给大家自己追吧,其实就是调用服务提供者中可能存在的boot方法

  • Illuminate\Foundation\Http\Kernel::handle方法中
protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');
	
    // 跳转到bootstrap方法
    $this->bootstrap();

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

public function bootstrap()
{   
    if (! $this->app->hasBeenBootstrapped()) {
        // bootstrapWith传递的就是上面的数组
        $this->app->bootstrapWith($this->bootstrappers());
    }
}

public function bootstrapWith(array $bootstrappers)
{   
    $this->hasBeenBootstrapped = true;

    foreach ($bootstrappers as $bootstrapper) {
        $this['events']->dispatch('bootstrapping: ' . $bootstrapper, [$this]);
		// 此文中$bootstrapper=\Illuminate\Foundation\Bootstrap\RegisterProviders
        $this->make($bootstrapper)->bootstrap($this);

        $this['events']->dispatch('bootstrapped: ' . $bootstrapper, [$this]);
    }
}
// 以上代码上篇已经说过,此处再走一遍流程

/**
 * Bootstrap the given application.
 *
 * @param  \Illuminate\Contracts\Foundation\Application  $app
 * @return void
 */
public function bootstrap(Application $app)
{	
    // 跳转到registerConfiguredProviders方法,今天要说的都在这个方法里了
    $app->registerConfiguredProviders();
}

public function registerConfiguredProviders()
{   
	// 访问了一个不存在的config属性,触发Container的__get方法 跳转一下
    // Collection使用了EnumeratesValues trait,其中make方法返回Collection实例
    // 我们携带app.providers数组跳转到Collection的构造方法
    // 在此打印下Collection::make($this->config['app.providers'])
    Illuminate\Support\Collection {#45 ▼
      #items: array:27 [▼
        0 => "Illuminate\Auth\AuthServiceProvider"
        1 => "Illuminate\Broadcasting\BroadcastServiceProvider"
        2 => "Illuminate\Bus\BusServiceProvider"
		...
        25 => "App\Providers\RouteServiceProvider"
        26 => "App\Providers\MyFacadeProvider"
      ]
    }
    // 链式调用partition方法 跳转一下
    $providers = Collection::make($this->config['app.providers'])
        ->partition(function ($provider) {
            return strpos($provider, 'Illuminate\\') === 0;
        });
    // 在此打印$providers
    Illuminate\Support\Collection {#32 ▼
      #items: array:2 [▼
        0 => Illuminate\Support\Collection {#43 ▼
          #items: array:22 [▼
            0 => "Illuminate\Auth\AuthServiceProvider"
            ...
            21 => "Illuminate\View\ViewServiceProvider"
          ]
        }
        1 => Illuminate\Support\Collection {#31 ▼
          #items: array:5 [▼
            22 => "App\Providers\AppServiceProvider"
            ...
            26 => "App\Providers\MyFacadeProvider"
          ]
        }
      ]
    }
    
    // [$this->make(PackageManifest::class)->providers()此方法以数组的形式返回扩展包中的服务提供者
    // 跳转到splice方法
    $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
    dump($provider);
     Illuminate\Support\Collection {#32 ▼
      #items: array:3 [▼
        0 => Illuminate\Support\Collection {#43 ▼
          #items: array:22 [▼
            0 => "Illuminate\Auth\AuthServiceProvider"
			...
            21 => "Illuminate\View\ViewServiceProvider"
          ]
        }
        1 => array:5 [▼
          0 => "Facade\Ignition\IgnitionServiceProvider"
		  ...
          4 => "NunoMaduro\Collision\Adapters\Laravel\CollisionServiceProvider"
        ]
        2 => Illuminate\Support\Collection {#31 ▼
          #items: array:5 [▼
            22 => "App\Providers\AppServiceProvider"
            ...
            26 => "App\Providers\MyFacadeProvider"
          ]
        }
      ]
    }
    
    
    // ProviderRepository 此类是真正注册service provider的地方
    // $this->getCachedServicesPath() 默认返回的是/bootstrap/cache/services.php绝对路径
    // 我们先看load方法中的参数,就是$provider属性中的所有数组元素,合并到一个大数组中并返回
    // 跳转到ProviderRepository类的构造方法
    (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
    ->load($providers->collapse()->toArray());
}

public function __get($key)
{	
    // 上篇讲过Container实现了ArrayAccess接口
    // 继续触发offsetGet方法 跳转一下
    // 聪明的道友可能已经想到可不可以通过__set方法直接绑定实现到容器呢? 当然可以
    // 有兴趣的可以自行查看__set方法
    return $this[$key];
}

public function offsetGet($key)
{	
    // 到此解析出了config实例 跳转回registerConfiguredProviders方法
    return $this->make($key);
}

Illuminate\Support\Collection文件
public function __construct($items = [])
{   
    $this->items = $this->getArrayableItems($items);
}

Illuminate\Support\Traits\EnumeratesValues文件
public function partition($key, $operator = null, $value = null)
{
    $passed = [];
    $failed = [];
	// 调用partition方法的时候,只传递了一个闭包
    $callback = func_num_args() === 1
		// 自己追下代码,$callable就是传递进来的闭包
        ? $this->valueRetriever($key)
        : $this->operatorForWhere(...func_get_args());

    foreach ($this as $key => $item) {
        // dump($key, $item);
        // 在此将service provider进行了分组,分组规则就在闭包中
        if ($callback($item, $key)) {
            $passed[$key] = $item;
        } else {
            $failed[$key] = $item;
        }
    }
	// 返回到registerConfiguredProviders方法并打印$providers
    return new static([new static($passed), new static($failed)]);
}

Illuminate\Support\Collection文件
public function splice($offset, $length = null, $replacement = [])
{
    if (func_num_args() === 1) {
        return new static(array_splice($this->items, $offset));
    }
    // array_splice 第三个参数为0表示在offset后面添加数组元素
    // 返回到registerConfiguredProviders方法并打印$providers
    return new static(array_splice($this->items, $offset, $length, $replacement));
}

Illuminate\Foundation\ProviderRepository文件中
public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath)
{
    $this->app = $app;
    $this->files = $files;
    $this->manifestPath = $manifestPath;
}

/**
 * Register the application service providers.
 *
 * @param  array  $providers
 * @return void
 */
public function load(array $providers)
{
	// 跳转到loadManifest方法
    $manifest = $this->loadManifest();
    
    // First we will load the service manifest, which contains information on all
    // service providers registered with the application and which services it
    // provides. This is used to know which services are "deferred" loaders.
    
    // 判断是否需要重新编译providers
    // 比较$manifest 和 $providers
    // 如果不同
    if ($this->shouldRecompile($manifest, $providers)) {
        // 若想进入此逻辑,可以手动删除bootstrap/cache下的services.php文件
        // 让app重新缓存 跳转到compileManifest方法
        $manifest = $this->compileManifest($providers);
    }

    // Next, we will register events to load the providers for each of the events
    // that it has requested. This allows the service provider to defer itself
    // while still getting automatically loaded when a certain event occurs.
    foreach ($manifest['when'] as $provider => $events) {
        // 注册事件 以后讲解
        $this->registerLoadEvents($provider, $events);
    }

    // We will go ahead and register all of the eagerly loaded providers with the
    // application so their services can be registered with the application as
    // a provided service. Then we will set the deferred service list on it.
    // dump($manifest); die;
    foreach ($manifest['eager'] as $provider) {
        // 如果不是延迟加载服务,那就在此阶段(也就是框架引导阶段直接调用服务的register方法)
        // 跳转到app的register方法 前面文章也有讲到
        $this->app->register($provider);
    }
	
    // 跳转到addDeferredServices方法,传递的数组都是要延迟加载的服务提供者
    $this->app->addDeferredServices($manifest['deferred']);
    // public function addDeferredServices(array $services)
    // {
    //     $this->deferredServices = array_merge($this->deferredServices, $services);
    // }
    // 下面来看Application的make方法
}

public function loadManifest()
{
    // The service manifest is a file containing a JSON representation of every
    // service provided by the application and whether its provider is using
    // deferred loading or should be eagerly loaded on each request to us.

    // load方法会判断文件是否存在
    // 如果/bootstrap/cache/services.php存在就加载其中的数组
    if ($this->files->exists($this->manifestPath)) {
        $manifest = $this->files->getRequire($this->manifestPath);
        // dump($manifest);
        array:4 [▼
          "providers" => array:32 [▼
            0 => "Illuminate\Auth\AuthServiceProvider"
            ...
          ]
          "eager" => array:19 [▼
            0 => "Illuminate\Auth\AuthServiceProvider"
            ...
          ]
          "deferred" => array:105 [▼
            "Illuminate\Broadcasting\BroadcastManager" => 				"Illuminate\Broadcasting\BroadcastServiceProvider"
            ...
          ]
          "when" => array:13 [▼
            "Illuminate\Broadcasting\BroadcastServiceProvider" => []
            ...
          ]
        ]
        if ($manifest) {
            // 返回到load方法
            return array_merge(['when' => []], $manifest);
        }
    }
}

// 比较文件缓存和传递的$providers是否相等 跳转回load方法
public function shouldRecompile($manifest, $providers)
{
    // dump($manifest, $providers);
    return is_null($manifest) || $manifest['providers'] != $providers;
}

protected function compileManifest($providers)
{
    // freshManifest方法返回数组 因为需要重新编译provider所以将when删除了
    // ['providers' => $providers, 'eager' => [], 'deferred' => []];
    $manifest = $this->freshManifest($providers);
    // 此时的$providers就是除了框架实例化时就注册的全部服务提供者了
    foreach ($providers as $provider) {
        // createProvider方法 return new $provider($this->app);
        $instance = $this->createProvider($provider);
        
        // When recompiling the service manifest, we will spin through each of the
        // providers and check if it's a deferred provider or not. If so we'll
        // add it's provided services to the manifest and note the provider.
		
        // 我们所有的Provider都继承自ServiceProvider基类
        // 如果实现了DeferrableProvider的provides接口就认为此服务要延迟加载
        if ($instance->isDeferred()) {
            // 判断一个服务是否延迟加载 需要实现Illuminate\Contracts\Support\DeferrableProvider接口的provides方法 返回一个数组
            foreach ($instance->provides() as $service) {
                // 老版本的laravel 如果想表明一个延迟服务 需要在对应的provider中 加上protected $defer = true属性
                // 现版本需要在provider中实现DeferrableProvider接口
                // 查看 Illuminate\Redis\RedisServiceProvider 其中的provides方法
                // public function provides()
                // {
                //     return ['redis', 'redis.connection'];
                // }
                $manifest['deferred'][$service] = $provider;
            }
			// 如果provider没有实现when方法 基类返回[]
            $manifest['when'][$provider] = $instance->when();
        }

        // If the service providers are not deferred, we will simply add it to an
        // array of eagerly loaded providers that will get registered on every
        // request to this application instead of "lazy" loading every time.
        else {
            // 如果不是一个延迟服务 那么直接将服务放到eager数组中
            $manifest['eager'][] = $provider;
        }
    }
    // 将服务提供者写入文件 跳转回load方法
    return $this->writeManifest($manifest);
}

Illuminate\Foundation\Application文件
public function register($provider, $force = false)
{   
    if (($registered = $this->getProvider($provider)) && !$force) {
        return $registered;
    }

    // If the given "provider" is a string, we will resolve it, passing in the
    // application instance automatically for the developer. This is simply
    // a more convenient way of specifying your service provider classes.

    // 可以看到register方法 是官方更加推荐的注册服务提供者的方式
    if (is_string($provider)) {
        // new一个provider
        $provider = $this->resolveProvider($provider);
    }

    // 调用服务提供者中的register方法
    $provider->register();

    // If there are bindings / singletons set as properties on the provider we
    // will spin through them and register them with the application, which
    // serves as a convenience layer while registering a lot of bindings.

    // 按道理来说每个serviceprovider都可以拥有这两个属性
    if (property_exists($provider, 'bindings')) {
        foreach ($provider->bindings as $key => $value) {
            $this->bind($key, $value);
        }
    }
    
    if (property_exists($provider, 'singletons')) {
        foreach ($provider->singletons as $key => $value) {
            $this->singleton($key, $value);
        }
    }

    // 标识该服务已经注册
    $this->markAsRegistered($provider);
    
    // If the application has already booted, we will call this boot method on
    // the provider class so it has an opportunity to do its boot logic and
    // will be ready for any usage by this developer's application logic.

    // 如果框架已经boot完毕 那么给后注册的provider执行boot方法
    if ($this->isBooted()) {
        // 还记得protected $booted属性在哪里设置为true的吗
        // 就在开篇的bootstrapWith方法中 表示框架已经引导完毕
        // 执行provider中的boot方法
        $this->bootProvider($provider);
    }
	// 跳转回load方法
    return $provider;
}

/**
 * Resolve the given type from the container.
 *
 * (Overriding Container::make) 
 *
 * @param  string  $abstract
 * @param  array  $parameters
 * @return mixed
 */
// 毫无疑问 重写了Container的make方法
public function make($abstract, array $parameters = [])
{   
    $abstract = $this->getAlias($abstract);
	
    if ($this->isDeferredService($abstract) && !isset($this->instances[$abstract])) {
        // 跳转吧
        $this->loadDeferredProvider($abstract);
    }

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

public function loadDeferredProvider($service)
{
    if (!$this->isDeferredService($service)) {
        return;
    }

    $provider = $this->deferredServices[$service];

    // If the service provider has not already been loaded and registered we can
    // register it with the application and remove the service from this list
    // of deferred services, since it will already be loaded on subsequent.
    if (!isset($this->loadedProviders[$provider])) {
        // 跳转吧
        $this->registerDeferredProvider($provider, $service);
    }
}

public function registerDeferredProvider($provider, $service = null)
{
    // Once the provider that provides the deferred service has been registered we
    // will remove it from our local list of the deferred services with related
    // providers so that this container does not try to resolve it out again.
    if ($service) {
        unset($this->deferredServices[$service]);
    }
	// 可以看到最终还是调用了register方法 完结!!!
    $this->register($instance = new $provider($this));

    if (!$this->isBooted()) {
        $this->booting(function () use ($instance) {
            $this->bootProvider($instance);
        });
    }
}

简单测试
Route::get('redis', function(){
    dump(resolve('app')->getDeferredServices());
    // 使用延迟解析
    dump(app()->app->getLoadedProviders()); // 没有redis对应的RedisServiceProvider
    dump(get_class(resolve('redis')));
    dump(app()['app']->getLoadedProviders()); // 有redis对应的ServiceProvider
});

到此服务注册算是完结了。

下集预告: 传递到handle方法中的$request到底是啥及laravel中的管道。

posted @ 2020-08-04 16:59  alwayslinger  阅读(2793)  评论(0编辑  收藏  举报