Facade在Laravel中的工作机制

房东的猫 提交于 2020-01-07 15:55:20

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

我们知道Facade门面模式是一个中介类,对子系统或者类对象进行封装和代理,为子系统的一组接口提供一个统一的高层接口。它对外暴露子系统的功能,但是不暴露子系统对象,用户要使用子系统的某个功能(方法)时,先调用门店,由于门店代理了子系统,所以委托子系统去处理相应的功能请求,从而达到将用户和子系统解耦的目的,用户只需要和门店打交道,不需要知道所有的子系统及其内部构造。

我们接下来通过在Laravel中最常用的DB-Facade,来看看Facade在Laravel中是如何工作的。

1,代码如下

<?php

use Exception;
use Illuminate\Support\Facades\DB;

Class A {
    function a() {
        try {
            DB::beginTransaction();
            //do something
            DB::commit();
        } catch (Exception $e) {
            DB::rollback();
            // handle exception
        }
    }
}

这里,我们看到use了一个DB的Facades,为什么写了DB两个字母,就会自动use DB的Facade呢?这就涉及到 Laravel的自动加载和依赖注入机制,这里略过。

我们来看看这个类文件的代码:

2, DB Facades

<?php

namespace Illuminate\Support\Facades;

/**
 * @method static \Illuminate\Database\ConnectionInterface connection(string $name = null)
 * @method static string getDefaultConnection()
 * @method static void setDefaultConnection(string $name)
 * @method static \Illuminate\Database\Query\Builder table(string $table)
 * @method static \Illuminate\Database\Query\Expression raw($value)
 * @method static mixed selectOne(string $query, array $bindings = [])
 * @method static array select(string $query, array $bindings = [])
 * @method static bool insert(string $query, array $bindings = [])
 * @method static int update(string $query, array $bindings = [])
 * @method static int delete(string $query, array $bindings = [])
 * @method static bool statement(string $query, array $bindings = [])
 * @method static int affectingStatement(string $query, array $bindings = [])
 * @method static bool unprepared(string $query)
 * @method static array prepareBindings(array $bindings)
 * @method static mixed transaction(\Closure $callback, int $attempts = 1)
 * @method static void beginTransaction()
 * @method static void commit()
 * @method static void rollBack()
 * @method static int transactionLevel()
 * @method static array pretend(\Closure $callback)
 *
 * @see \Illuminate\Database\DatabaseManager
 * @see \Illuminate\Database\Connection
 */
class DB extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'db';
    }
}

发现它只有一个getFacadeAccessor静态方法,返回一个'db'的字符串,其他方法都没有。

那它到底是怎么执行的其他方法呢,比如这里的beginTranscation()方法?我们知道,当调用一个不存在的静态方法时,PHP会自动调用__callStatic()魔术方法,而这里没有,那我们只能从它继承的Facade父类中看看。

3,Facade父类 / 基类

<?php

namespace Illuminate\Support\Facades;

use Closure;
use Mockery;
use RuntimeException;
use Mockery\MockInterface;

abstract class Facade
{
    /**
     * The application instance being facaded.
     *
     * @var \Illuminate\Contracts\Foundation\Application
     */
    protected static $app;

    /**
     * The resolved object instances.
     *
     * @var array
     */
    protected static $resolvedInstance;

    /**
     * Run a Closure when the facade has been resolved.
     *
     * @param  \Closure  $callback
     * @return void
     */
    public static function resolved(Closure $callback)
    {
        static::$app->afterResolving(static::getFacadeAccessor(), function ($service) use ($callback) {
            $callback($service);
        });
    }

    /**
     * Convert the facade into a Mockery spy.
     *
     * @return \Mockery\MockInterface
     */
    public static function spy()
    {
        if (! static::isMock()) {
            $class = static::getMockableClass();

            return tap($class ? Mockery::spy($class) : Mockery::spy(), function ($spy) {
                static::swap($spy);
            });
        }
    }

    /**
     * Initiate a mock expectation on the facade.
     *
     * @return \Mockery\Expectation
     */
    public static function shouldReceive()
    {
        $name = static::getFacadeAccessor();

        $mock = static::isMock()
                    ? static::$resolvedInstance[$name]
                    : static::createFreshMockInstance();

        return $mock->shouldReceive(...func_get_args());
    }

    /**
     * Create a fresh mock instance for the given class.
     *
     * @return \Mockery\Expectation
     */
    protected static function createFreshMockInstance()
    {
        return tap(static::createMock(), function ($mock) {
            static::swap($mock);

            $mock->shouldAllowMockingProtectedMethods();
        });
    }

    /**
     * Create a fresh mock instance for the given class.
     *
     * @return \Mockery\MockInterface
     */
    protected static function createMock()
    {
        $class = static::getMockableClass();

        return $class ? Mockery::mock($class) : Mockery::mock();
    }

    /**
     * Determines whether a mock is set as the instance of the facade.
     *
     * @return bool
     */
    protected static function isMock()
    {
        $name = static::getFacadeAccessor();

        return isset(static::$resolvedInstance[$name]) &&
               static::$resolvedInstance[$name] instanceof MockInterface;
    }

    /**
     * Get the mockable class for the bound instance.
     *
     * @return string|null
     */
    protected static function getMockableClass()
    {
        if ($root = static::getFacadeRoot()) {
            return get_class($root);
        }
    }

    /**
     * Hotswap the underlying instance behind the facade.
     *
     * @param  mixed  $instance
     * @return void
     */
    public static function swap($instance)
    {
        static::$resolvedInstance[static::getFacadeAccessor()] = $instance;

        if (isset(static::$app)) {
            static::$app->instance(static::getFacadeAccessor(), $instance);
        }
    }

    /**
     * Get the root object behind the facade.
     *
     * @return mixed
     */
    public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }

    /**
     * Get the registered name of the component.
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    protected static function getFacadeAccessor()
    {
        throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
    }

    /**
     * Resolve the facade root instance from the container.
     *
     * @param  string|object  $name
     * @return mixed
     */
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }

        return static::$resolvedInstance[$name] = static::$app[$name];
    }

    /**
     * Clear a resolved facade instance.
     *
     * @param  string  $name
     * @return void
     */
    public static function clearResolvedInstance($name)
    {
        unset(static::$resolvedInstance[$name]);
    }

    /**
     * Clear all of the resolved instances.
     *
     * @return void
     */
    public static function clearResolvedInstances()
    {
        static::$resolvedInstance = [];
    }

    /**
     * Get the application instance behind the facade.
     *
     * @return \Illuminate\Contracts\Foundation\Application
     */
    public static function getFacadeApplication()
    {
        return static::$app;
    }

    /**
     * Set the application instance.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public static function setFacadeApplication($app)
    {
        static::$app = $app;
    }

    /**
     * Handle dynamic, static calls to the object.
     *
     * @param  string  $method
     * @param  array   $args
     * @return mixed
     *
     * @throws \RuntimeException
     */
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }
}

这里的基类是给所有Facade子类继承的,很明显,是不可能有子类的静态方法的。所以只能看__callStatic()方法,也就是最后这个。

    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }

它的第一步,是获取在Facade后面的 根对象 (root object)。

   /**
     * Get the root object behind the facade.
     *
     * @return mixed
     */
    public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }

这里的 static::getFacadeAccessor()方法是不是很眼熟,对,它就是DB-Facade类里面唯一的一个方法,返回字符串'db'。OK,我们看它的主方法resolveFacadeInstance,代码如下。它会从容器中解析 facade 根实例。

   /**
     * Resolve the facade root instance from the container.
     *
     * @param  string|object  $name
     * @return mixed
     */
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }

        return static::$resolvedInstance[$name] = static::$app[$name];
    }

如果是第一次解析,它就会从 static::$app这个数组中,去获取key 为 字符串 'db' 的值。

那我们看看 static::$app是什么。

    /**
     * The application instance being facaded.
     *
     * @var \Illuminate\Contracts\Foundation\Application
     */
    protected static $app;

是正在被门店代理的应用实例,来自于 \Illuminate\Contracts\Foundation\Application.

这是一个契约,有契约就会有实现契约的类,全局搜一下整个框架,发现,唯一的实现,是在 namespace Illuminate\Foundation\Application 下 (vendor/laravel/framework/src/Illuminate/Foundation/Application.php),我们来看看

<?php

namespace Illuminate\Foundation;

.
.
use Symfony\Component\HttpKernel\HttpKernelInterface;
.
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Contracts\Foundation\Application as ApplicationContract;

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    /**
     * Create a new Illuminate application instance.
     *
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        if ($basePath) {
            $this->setBasePath($basePath);
        }

        $this->registerBaseBindings();

        $this->registerBaseServiceProviders();

        $this->registerCoreContainerAliases();
    }

}

代码太多,就截取了最关键的部分。可以看到这个类实现了上面的 ApplicationContract。

看到构造方法,其实很熟悉了,是在将各种基本的绑定,基本的服务提供者,还有核心容器别名,注入到 服务容器中,也就是 $app中。

也就是说,我们要在服务容器$app中,找一个名为 'db' 的 facade 根实例。

2,$app

我们知道,在 Laravel 的入口文件 index.php文件中,第一步是注册自动加载器,第二步实例化出服务容器 $app,第三步是将内核启动,处理请求。

其中在处理请求中,有一个步骤是 bootstrap(),为HTTP请求运行起应用。

    /**
     * Bootstrap the application for HTTP requests.
     *
     * @return void
     */
    public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }

这里会让服务容器 $app 将 很多需要启动的组件给启动起来,如下:

    /**
     * The bootstrap classes for the application.
     *
     * @var array
     */
    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,
    ];

而 $this->app->bootstrapWith() 是怎么处理这些 bootstrap 类呢?

这里的bootstrapWith() 方法,点进去,它是在  vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php 文件中,

namespace Illuminate\Contracts\Foundation;

use Closure;
use Illuminate\Contracts\Container\Container;

interface Application extends Container
{
   /**
     * Run the given array of bootstrap classes.
     *
     * @param  array  $bootstrappers
     * @return void
     */
    public function bootstrapWith(array $bootstrappers);

}
    

又是一个接口,那谁实现的呢?就是上面提到的vendor/laravel/framework/src/Illuminate/Foundation/Application.php,代码如下

   /**
     * Run the given array of bootstrap classes.
     *
     * @param  string[]  $bootstrappers
     * @return void
     */
    public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

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

            $this->make($bootstrapper)->bootstrap($this);

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

这里传入一个数组,然后实例化他们,获取到对象后,分别调用了各自的bootstrap方法。

我们看两个:RegisterFacades 和 RegisterProviders。

2.1 RegisterFacades 的 bootstrap()

文件: vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php

<?php

namespace Illuminate\Foundation\Bootstrap;

use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\Facades\Facade;
use Illuminate\Foundation\PackageManifest;
use Illuminate\Contracts\Foundation\Application;

class RegisterFacades
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        Facade::clearResolvedInstances();

        Facade::setFacadeApplication($app);

        AliasLoader::getInstance(array_merge(
            $app->make('config')->get('app.aliases', []),
            $app->make(PackageManifest::class)->aliases()
        ))->register();
    }
}

它关键的一步,是将config/app.php文件中的aliases这些 类的别名 和 具体的 Facades,加上PackageManifest包列表的别名,一起打包, 将这些映射关系 调用 register() 方法,进行注册。注意以下的注释部分,这些别名是被 懒加载 处理的,也就是说,等到程序要用的时候才会真正被加载进来,所以不会影响性能。

/*
    |--------------------------------------------------------------------------
    | Class Aliases
    |--------------------------------------------------------------------------
    |
    | This array of class aliases will be registered when this application
    | is started. However, feel free to register as many as you wish as
    | the aliases are "lazy" loaded so they don't hinder performance.
    |
    */

    'aliases' => [

        'App'          => Illuminate\Support\Facades\App::class,
        .
        'DB'           => Illuminate\Support\Facades\DB::class,

    ],

这是RegisterFacades部分。我们再看 RegisterProviders。

2.2 RegisterProviders

目录: vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php

<?php

namespace Illuminate\Foundation\Bootstrap;

use Illuminate\Contracts\Foundation\Application;

class RegisterProviders
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        $app->registerConfiguredProviders();
    }
}

这里的registerConfiguredProviders()方法点进去,又是一个接口:

目录: vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php

<?php

namespace Illuminate\Contracts\Foundation;

use Illuminate\Contracts\Container\Container;

interface Application extends Container
{   
   /**
     * Register all of the configured providers.
     *
     * @return void
     */
    public function registerConfiguredProviders();
}

具体的实现,还是在 vendor/laravel/framework/src/Illuminate/Foundation/Application.php

<?php

namespace Illuminate\Foundation;


use Illuminate\Support\ServiceProvider;
use Illuminate\Events\EventServiceProvider;
use Illuminate\Routing\RoutingServiceProvider;
use Symfony\Component\HttpKernel\HttpKernelInterface;
.
use Illuminate\Contracts\Http\Kernel as HttpKernelContract;
.
use Illuminate\Contracts\Foundation\Application as ApplicationContract;

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
   /**
     * Register all of the configured providers.
     *
     * @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());
    }
}

它会将config/app.php配置文件的providers, 以 'Illuminate\\' 开头的类 当做第一部分,其他的为第二部分。然后将 packageManifest插入到中间,最后,providers就是这个三个元素的数组,如下:

object(Illuminate\Support\Collection)#32 (1) {
  ["items":protected]=>
  array(3) {
    [0]=>
    object(Illuminate\Support\Collection)#22 (1) {
      ["items":protected]=>
      array(22) {
        [0]=>
        string(35) "Illuminate\Auth\AuthServiceProvider"
        [1]=>
        string(48) "Illuminate\Broadcasting\BroadcastServiceProvider"
        [2]=>
        string(33) "Illuminate\Bus\BusServiceProvider"
        [3]=>
        string(37) "Illuminate\Cache\CacheServiceProvider"
        [4]=>
        string(61) "Illuminate\Foundation\Providers\ConsoleSupportServiceProvider"
        [5]=>
        string(39) "Illuminate\Cookie\CookieServiceProvider"
        [6]=>
        string(43) "Illuminate\Database\DatabaseServiceProvider"
        [7]=>
        string(47) "Illuminate\Encryption\EncryptionServiceProvider"
        [8]=>
        string(47) "Illuminate\Filesystem\FilesystemServiceProvider"
        [9]=>
        string(57) "Illuminate\Foundation\Providers\FoundationServiceProvider"
        [10]=>
        string(38) "Illuminate\Hashing\HashServiceProvider"
        [11]=>
        string(35) "Illuminate\Mail\MailServiceProvider"
        [12]=>
        string(52) "Illuminate\Notifications\NotificationServiceProvider"
        [13]=>
        string(47) "Illuminate\Pagination\PaginationServiceProvider"
        [14]=>
        string(43) "Illuminate\Pipeline\PipelineServiceProvider"
        [15]=>
        string(37) "Illuminate\Queue\QueueServiceProvider"
        [16]=>
        string(37) "Illuminate\Redis\RedisServiceProvider"
        [17]=>
        string(54) "Illuminate\Auth\Passwords\PasswordResetServiceProvider"
        [18]=>
        string(41) "Illuminate\Session\SessionServiceProvider"
        [19]=>
        string(49) "Illuminate\Translation\TranslationServiceProvider"
        [20]=>
        string(47) "Illuminate\Validation\ValidationServiceProvider"
        [21]=>
        string(35) "Illuminate\View\ViewServiceProvider"
      }
    }
    [1]=>
    array(9) {
      [0]=>
      string(43) "Fideloper\Proxy\TrustedProxyServiceProvider"
      [1]=>
      string(37) "Jacobcyl\AliOSS\AliOssServiceProvider"
      [2]=>
      string(52) "Illuminate\Notifications\NexmoChannelServiceProvider"
      [3]=>
      string(52) "Illuminate\Notifications\SlackChannelServiceProvider"
      [4]=>
      string(36) "Laravel\Tinker\TinkerServiceProvider"
      [5]=>
      string(38) "Maatwebsite\Excel\ExcelServiceProvider"
      [6]=>
      string(30) "Carbon\Laravel\ServiceProvider"
      [7]=>
      string(45) "SimpleSoftwareIO\QrCode\QrCodeServiceProvider"
      [8]=>
      string(43) "Spatie\Permission\PermissionServiceProvider"
    }
    [2]=>
    object(Illuminate\Support\Collection)#30 (1) {
      ["items":protected]=>
      array(8) {
        [22]=>
        string(32) "App\Providers\AppServiceProvider"
        [23]=>
        string(33) "App\Providers\AuthServiceProvider"
        [24]=>
        string(34) "App\Providers\EventServiceProvider"
        [25]=>
        string(34) "App\Providers\RouteServiceProvider"
        [26]=>
        string(39) "App\Providers\ApiRequestServiceProvider"
        [27]=>
        string(37) "Jacobcyl\AliOSS\AliOssServiceProvider"
        [28]=>
        string(38) "Maatwebsite\Excel\ExcelServiceProvider"
        [29]=>
        string(33) "App\Providers\TestServiceProvider"
      }
    }
  }
}

然后用 vendor/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php 这个服务提供者仓库(ProviderRepository),将这些服务提供者,逐个load 加载注册 到 $app 中。

<?php

namespace Illuminate\Foundation;

use Exception;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Contracts\Foundation\Application as ApplicationContract;

class ProviderRepository
{
    /**
     * Register the application service providers.
     *
     * @param  array  $providers
     * @return void
     */
    public function load(array $providers)
    {
        $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.
        if ($this->shouldRecompile($manifest, $providers)) {
            $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.
        foreach ($manifest['eager'] as $provider) {
            $this->app->register($provider);
        }

        $this->app->addDeferredServices($manifest['deferred']);
    }
}

执行顺序如下:

2.2.1 如果存在服务提供者缓存清单,则直接读取「服务提供者」集合;

2.2.2 否则,将从 config/app.php 配置中的服务提供者编译到缓存清单中;

2.2.2.1 这个处理由 compileManifest() 方法完成,之后$manifest是一个数组,含 providers, eager, when, deferred四个字段,如下

array(4) {
  ["when"]=>
  array(14) {
    ["Illuminate\Broadcasting\BroadcastServiceProvider"]=>
    array(0) {
    }
    ["Illuminate\Bus\BusServiceProvider"]=>
    array(0) {
    }
    ["Illuminate\Cache\CacheServiceProvider"]=>
    array(0) {
    }
    .
  }
  ["providers"]=>
  array(39) {
    [0]=>
    string(35) "Illuminate\Auth\AuthServiceProvider"
    [1]=>
    string(48) "Illuminate\Broadcasting\BroadcastServiceProvider"
    [2]=>
    string(33) "Illuminate\Bus\BusServiceProvider"
    [3]=>
    string(37) "Illuminate\Cache\CacheServiceProvider"
    [4]=>
    string(61) "Illuminate\Foundation\Providers\ConsoleSupportServiceProvider"
    [5]=>
    string(39) "Illuminate\Cookie\CookieServiceProvider"
    [6]=>
    string(43) "Illuminate\Database\DatabaseServiceProvider"
     .
    [31]=>
    string(32) "App\Providers\AppServiceProvider"
    [32]=>
    string(33) "App\Providers\AuthServiceProvider"
    [33]=>
    string(34) "App\Providers\EventServiceProvider"
    [34]=>
    string(34) "App\Providers\RouteServiceProvider"
    [35]=>
    string(39) "App\Providers\ApiRequestServiceProvider"
    [36]=>
    string(37) "Jacobcyl\AliOSS\AliOssServiceProvider"
    [37]=>
    string(38) "Maatwebsite\Excel\ExcelServiceProvider"
    [38]=>
    string(33) "App\Providers\TestServiceProvider"
  }
  ["eager"]=>
  array(25) {
    [0]=>
    string(35) "Illuminate\Auth\AuthServiceProvider"
    [1]=>
    string(39) "Illuminate\Cookie\CookieServiceProvider"
    [2]=>
    string(43) "Illuminate\Database\DatabaseServiceProvider"
    [3]=>
    string(47) "Illuminate\Encryption\EncryptionServiceProvider"
    [4]=>
    string(47) "Illuminate\Filesystem\FilesystemServiceProvider"
    [5]=>
    string(57) "Illuminate\Foundation\Providers\FoundationServiceProvider"
    .
    [17]=>
    string(32) "App\Providers\AppServiceProvider"
    [18]=>
    string(33) "App\Providers\AuthServiceProvider"
    [19]=>
    string(34) "App\Providers\EventServiceProvider"
    [20]=>
    string(34) "App\Providers\RouteServiceProvider"
    [21]=>
    string(39) "App\Providers\ApiRequestServiceProvider"
    [22]=>
    string(37) "Jacobcyl\AliOSS\AliOssServiceProvider"
    [23]=>
    string(38) "Maatwebsite\Excel\ExcelServiceProvider"
    [24]=>
    string(33) "App\Providers\TestServiceProvider"
  }
  ["deferred"]=>
  array(103) {
    ["Illuminate\Broadcasting\BroadcastManager"]=>
    string(48) "Illuminate\Broadcasting\BroadcastServiceProvider"
    ["Illuminate\Contracts\Broadcasting\Factory"]=>
    string(48) "Illuminate\Broadcasting\BroadcastServiceProvider"
    ["Illuminate\Contracts\Broadcasting\Broadcaster"]=>
    string(48) "Illuminate\Broadcasting\BroadcastServiceProvider"
    ["Illuminate\Bus\Dispatcher"]=>
    string(33) "Illuminate\Bus\BusServiceProvider"
    ["Illuminate\Contracts\Bus\Dispatcher"]=>
    string(33) "Illuminate\Bus\BusServiceProvider"
    ["Illuminate\Contracts\Bus\QueueingDispatcher"]=>
    string(33) "Illuminate\Bus\BusServiceProvider"
    ["cache"]=>
    string(37) "Illuminate\Cache\CacheServiceProvider"
    ["cache.store"]=>
    string(37) "Illuminate\Cache\CacheServiceProvider"
    ["memcached.connector"]=>
    string(37) "Illuminate\Cache\CacheServiceProvider"
    ["command.cache.clear"]=>
    string(61) "Illuminate\Foundation\Providers\ConsoleSupportServiceProvider"
    .
    string(61) "Illuminate\Foundation\Providers\ConsoleSupportServiceProvider"
    ["migrator"]=>
    .
  }
}

我们看到 config/app.php中的providers都被加定义在eager字段下,也就是饥渴加载。

2.2.2.2 编译缓存清单时将处理 贪婪/饥渴加载(eager)和 延迟加载(deferred)的服务提供者;

2.2.3 对于贪婪加载的提供者直接执行服务容器的 register 方法完成服务注册;

2.2.4 将延迟加载提供者加入到服务容器中,按需注册和引导启动。

这边关注需要被饥渴加载的服务提供则,它调用了 register()方法,同样是 vendor/laravel/framework/src/Illuminate/Contracts/Foundation/Application.php 这个 契约接口,仍然是由 Illuminate\Foundation\Application 来实现的,具体如下:

/**
     * Register a service provider with the application.
     *
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @param  bool   $force
     * @return \Illuminate\Support\ServiceProvider
     */
    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.
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }

        if (method_exists($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.
        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.
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

关键是这一步,如果provider的控制器中有register方法,则执行该方法。

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

我们在上面的eager字段中包含了 Illuminate\Database\DatabaseServiceProvider 这个服务提供者,让我们来看看它的注册方法。

<?php

namespace Illuminate\Database;

.
.

class DatabaseServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application events.
     *
     * @return void
     */
    public function boot()
    {
        Model::setConnectionResolver($this->app['db']);

        Model::setEventDispatcher($this->app['events']);
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        Model::clearBootedModels();

        $this->registerConnectionServices();

        $this->registerEloquentFactory();

        $this->registerQueueableEntityResolver();
    }

    /**
     * Register the primary database bindings.
     *
     * @return void
     */
    protected function registerConnectionServices()
    {
        // The connection factory is used to create the actual connection instances on
        // the database. We will inject the factory into the manager so that it may
        // make the connections while they are actually needed and not of before.
        $this->app->singleton('db.factory', function ($app) {
            return new ConnectionFactory($app);
        });

        // The database manager is used to resolve various connections, since multiple
        // connections might be managed. It also implements the connection resolver
        // interface which may be used by other components requiring connections.
        $this->app->singleton('db', function ($app) {
            return new DatabaseManager($app, $app['db.factory']);
        });

        $this->app->bind('db.connection', function ($app) {
            return $app['db']->connection();
        });
    }
 .
 .
 .
}

我们看到它的register()方法调用了registerConnectionServices,而在这个方法里面,我们看到第二步,把 DatabaseManager对象以 'db' 的别名 注册到了 $app 容器中。

百转千回,山重水复,至此,真相终于大白。

原来 Facade 要解析的根对象,就来自于被注册到服务容器$app中的 服务提供者。

   /**
     * Resolve the facade root instance from the container.
     *
     * @param  string|object  $name
     * @return mixed
     */
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }

        return static::$resolvedInstance[$name] = static::$app[$name];
    }

在 调用 DB Facade 中的 beginTransaction() 方法时,实际是由 Illuminate\Database\DatabaseServiceProvider 这个服务提供者 中的 DatabaseManager 去执行的,他们通过 Facade的别名 'db',以及 服务提供者下的DatabaseManager的别名 'db' 进行关联的。

 

额外说一点,我们在说 将应用启动时,只讲了 RegisterFacades 和 RegisterProviders,其实下面还有一步 BootProviders. 它的步骤和 RegisterProviders差不多,也是通过应用契约,最后让应用去调用每个Provider的方法,只不过RegisterProviders调用的是register()方法,而BootProviders调用的是boot()方法。一个是注册,一个是启动。

    /**
     * The bootstrap classes for the application.
     *
     * @var array
     */
    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,
    ];

至此,我们把Facade在Laravel中的工作机制跑通了一遍,顺带着把服务提供者的注册和启动也过了一遍,让我们对Laravel的请求生命周期也有了更深的了解。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!