Laravel 走进服务提供者的世界

1,099 阅读4分钟

Hello,我是Rocket

这是我参与更文挑战的第5天,活动详情查看:更文挑战

引言

  • 服务提供者的功能是完成 Laravel 应用的引导启动,或者说是将 Laravel 中的各种服务「注册」到「Laravel 服务容器」,这样才能在后续处理 HTTP 请求时使用这些服务
  • 如果说IOC容器是Laravel的房子,那么服务提供者就是支柱,就是基石
  • 不了解IOC的同学,看我另外的文章传送门

1、概念

  • 主要工作是使用「IOC容器」实现服务容器绑定、事件监听器、中间件,甚至是路由的注册
  • 除核心服务外,几乎所有的服务提供者都定义在配置文件 config/app.php 文件中的 providers
  • 基础的处理流程,Laravel 应用接收到 HTTP 请求时会去执行注册服务提供者,然后到了实际处理阶段,依据使用情况按需加载所需服务。
  • 核心的服务提供者在Illuminate\Foundation\Application 容器实例化就已经做了注册
<?php
...

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    public function __construct($basePath = null)
    {
        ...
        $this->registerBaseServiceProviders();
        ...
    }

    /**
     * Register all of the base service providers. 注册应用基础服务提供者
     *
     * @return void
     */
    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));

        $this->register(new LogServiceProvider($this));

        $this->register(new RoutingServiceProvider($this));
    }

2、服务提供者的注册

2.1、通过引导程序注册服务提供者

服务提供者 注册 和 引导启动 直到处理 HTTP 请求阶段才开始。所以我们直接进入到 App\Console\Kernel 类,同时这个类继承于 Illuminate\Foundation\Http\Kernel 类。

class Kernel implements KernelContract
{
    ...

    /**
     * The bootstrap classes for the application. 应用引导类
     */
    protected $bootstrappers = [
        ...
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class, // 用于注册(register)「服务提供者」的引导类
        \Illuminate\Foundation\Bootstrap\BootProviders::class, // 用于启动(boot)「服务提供者」的引导类
    ];

 
    public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride();

            $response = $this->sendRequestThroughRouter($request);
        } catch (Exception $e) {
            ...
        } catch (Throwable $e) {
            ...
        }

        ...
    }

 
    protected function sendRequestThroughRouter($request)
    {
        ...

        // 1. 引导类引导启动。
        $this->bootstrap();

        // 2. 中间件及请求处理,生成响应并返回响应。
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

    /**
     * Bootstrap the application for HTTP requests. 接收 HTTP 请求时启动应用引导程序。
     */
    public function bootstrap()
    {
        // 引导类启动由 Application 容器引导启动。
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }
}

启动引导程序通过 $this->bootstrap ()方法完成,其中包括所有服务提供者的注册和引导处理

进入Illuminate\Foundation\Application 容器中的 bootstrapWith() 方法,来看看容器是如何将引导类引导启动的

    /**
     * Run the given array of bootstrap classes. 执行给定引导程序
     */
    public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

            // 从容器中解析出实例,然后调用实例的 bootstrap() 方法引导启动。 
            $this->make($bootstrapper)->bootstrap($this);

            $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
        }
    }
    
   

2.2 注册服务引导类的处理

追踪到Illuminate\Foundation\Bootstrap\RegisterProviders 实际调用的是 Illuminate\Foundation\Application 的 registerConfiguredProviders


 public function registerConfiguredProviders()
    {

        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($providers->collapse()->toArray());
    }
    
 public function getCachedServicesPath()
    {
        return $this->bootstrapPath().'/cache/services.php';
    }

2.3 由服务提供者仓库(ProviderRepository)执行服务提供者的注册处理

<?php

namespace Illuminate\Foundation;

class ProviderRepository
{
    ...

    /**
     * Register the application service providers. 注册应用的服务提供者。
     */
    public function load(array $providers)
    {
        //加载缓存清单
        $manifest = $this->loadManifest();

        //判断是否需要生成缓存清单
        if ($this->shouldRecompile($manifest, $providers)) {
            $manifest = $this->compileManifest($providers);
        }
        //注册加载事件(延迟加载服务的激活手段之一,通过事件注册)
        foreach ($manifest['when'] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }

        // 到这里,先执行应用必要(贪婪)的服务提供者完成服务注册。
        foreach ($manifest['eager'] as $provider) {
            $this->app->register($provider);
        }

        // 最后将所有「延迟加载服务提供者」加入到容器中。
        $this->app->addDeferredServices($manifest['deferred']);
    }

    /**
     * 将服务提供者编译到清单文件中缓存起来。
     */
    protected function compileManifest($providers)
    {
        $manifest = $this->freshManifest($providers);

        foreach ($providers as $provider) {
            // 解析出 $provider 对应的实例
            $instance = $this->createProvider($provider);

            // 判断当前服务提供者是否为「延迟加载」类
            if ($instance->isDeferred()) {
                foreach ($instance->provides() as $service) {
                    $manifest['deferred'][$service] = $provider;
                }

                $manifest['when'][$provider] = $instance->when();
            }

            // 如果不是「延迟加载」类型的服务提供者,则为贪婪加载必须立即去执行注册方法。
            else {
                $manifest['eager'][] = $provider;
            }
        }

        // 将归类后的服务提供者写入清单文件。
        return $this->writeManifest($manifest);
    }
    //注册事件
   protected function registerLoadEvents($provider, array $events)
{
    if (count($events) < 1) {
        return;
    }

    $this->app->make('events')->listen($events, function () use ($provider) {
        $this->app->register($provider);
    });
}

2.4 缓存清单

  • 缓存文件中 providers 放入了所有自定义和框架核心的服务。
  • eager 数组中放入了所有需要立即启动的服务提供者。
  • deferred 数组中放入了所有需要延迟加载的服务提供者。
  • when 放入了延迟加载需要激活的事件。
return array (
    'providers' => 
    array (
      0 => 'Illuminate\\Auth\\AuthServiceProvider',
      1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
      ...
    ),

    'eager' => 
    array (
      0 => 'Illuminate\\Auth\\AuthServiceProvider',
      1 => 'Illuminate\\Cookie\\CookieServiceProvider',
      ...
    ),

    'deferred' => 
    array (
      'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
      'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
      ...
    ),

    'when' => 
    array (
      'Illuminate\\Broadcasting\\BroadcastServiceProvider' => 
      array (
      ),
      ...
    ),

2.5 Illuminate\Foundation\Application 容器完成注册

    /**
     *  在应用服务容器中注册一个服务提供者。
     */
    public function register($provider, $options = [], $force = false)
    {   
        //如果服务已注册直接返回
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        // 如果给定的服务提供者是接口名称,解析出它的实例。
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }

        // 服务提供者提供注册方法时,执行注册服务处理
        if (method_exists($provider, 'register')) {
            $provider->register();
        }
        // 通过服务属性绑定
        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);

        // 判断 Laravel 应用是否已启动。已启动的话需要去执行启动处理。延迟服务提供者
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }
    
     protected function markAsRegistered($provider)
    {
        $this->serviceProviders[] = $provider;

        $this->loadedProviders[get_class($provider)] = true;
    }
    
     protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }

3、服务提供者的启动

服务容器的启动由类 \Illuminate\Foundation\Bootstrap\BootProviders 负责:

3.1 BootProviders 引导启动

class BootProviders
{
    public function bootstrap(Application $app)
    {
        $app->boot();
    }
}

3.2 由服务容器执行配置文件中的所有服务提供者服务完成启动


class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    public function boot()
    {
        if ($this->booted) {
            return;
        }
        //执行启动前置回调
        $this->fireAppCallbacks($this->bootingCallbacks);
        //依次启动所有服务
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);
        });
        //将启动状态置为true
        $this->booted = true;
        //执行启动后置回调
        $this->fireAppCallbacks($this->bootedCallbacks);
    }

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

3.3 延迟服务提供者的启动

对于延迟加载类型的服务提供者,我们要到使用时才会去执行它们内部的 register 和 boot 方法,也就是解析

Illuminate\Foundation\Applicationmake

    /**
     * 从容器中解析出给定服务
     */
    public function make($abstract, array $parameters = [])
    {
        $abstract = $this->getAlias($abstract);

        // 判断这个接口是否为延迟类型的并且没有被解析过,是则去将它加载到容器中。
        if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) {
            $this->loadDeferredProvider($abstract);
        }

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

    /**
     *  加载给定延迟加载服务提供者
     */
    public function loadDeferredProvider($service)
    {
        if (! isset($this->deferredServices[$service])) {
            return;
        }

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

        // 如果服务未注册则去注册并从延迟服务提供者集合中删除它。
        if (! isset($this->loadedProviders[$provider])) {
            $this->registerDeferredProvider($provider, $service);
        }
    }
    
   
    /**
     * 去执行服务提供者的注册方法。
     */
    public function registerDeferredProvider($provider, $service = null)
    {
        if ($service) {
            unset($this->deferredServices[$service]);
        }
        // 执行服务提供者注册服务 (如果应用已启动会执行boot)参考 2.5
        $this->register($instance = new $provider($this)); 
        // 执行服务提供者启动服务。
        if (! $this->booted) {
            //注册回调  (参考3.2)
            $this->booting(function () use ($instance) {
                $this->bootProvider($instance);
            });
        }
    }
    
    public function booting($callback)
    {
        $this->bootingCallbacks[] = $callback;
    }

4、流程图

流程.png

5、结尾

与诸君共勉之,希望对您有所帮助