Laravel源码(4):Facade是个啥?

laravel框架给人第一感觉是什么?我想绝大多数人都会说晦涩难懂。作者泰勒把php的很多特性都进行了封装,并给出优雅的接口,同时也创早了很多新词来描述这种封装。从某种角度看,这其实代表了作者的某种设计哲学。Facade应该算是对类和对象的一种封装。今天就来看看Faades到底是个啥。

1 官方文档的定义

“Facades 为应用的 服务容器 提供了一个「静态」 接口。Laravel 自带了很多 Facades,可以访问绝大部分功能。Laravel Facades 实际是服务容器中底层类的 「静态代理」 ,相对于传统静态方法,在使用时能够提供更加灵活、更加易于测试、更加优雅的语法。”

如何使用Facades?

#所有的 Laravel Facades 都定义在 Illuminate\Support\Facades 命名空间下。
#所以,我们可以轻松的使用 Facade :
use Illuminate\Support\Facades\Cache;

Route::get('/cache', function () {
    return Cache::get('key');
});

这个实例导入了缓存类的Facade,这个Facade是一个定义在Illuminate\Support\Facades命名空间下的Cache类。关于缓存类的具体实现还有待深挖。当然Facade的更通用的用法是省略前缀Illuminate\Support\Facades,直接use Cache进来,或者直接在代码中以\Cache::get()的方式调用,因为在config目录下的app.php文件中已经为我们导入了类别名。

'aliases' => [
        'App' => Illuminate\Support\Facades\App::class,
        'Artisan' => Illuminate\Support\Facades\Artisan::class,
        'Auth' => Illuminate\Support\Facades\Auth::class,
        'Blade' => Illuminate\Support\Facades\Blade::class,
        'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
        'Bus' => Illuminate\Support\Facades\Bus::class,
        'Cache' => Illuminate\Support\Facades\Cache::class,
        'Config' => Illuminate\Support\Facades\Config::class,
        'Cookie' => Illuminate\Support\Facades\Cookie::class,
        'Crypt' => Illuminate\Support\Facades\Crypt::class,
        'DB' => Illuminate\Support\Facades\DB::class,
        'Eloquent' => Illuminate\Database\Eloquent\Model::class,
        'Event' => Illuminate\Support\Facades\Event::class,
        'File' => Illuminate\Support\Facades\File::class,
        'Gate' => Illuminate\Support\Facades\Gate::class,
        'Hash' => Illuminate\Support\Facades\Hash::class,
        'Lang' => Illuminate\Support\Facades\Lang::class,
        'Log' => Illuminate\Support\Facades\Log::class,
        'Mail' => Illuminate\Support\Facades\Mail::class,
        'Notification' => Illuminate\Support\Facades\Notification::class,
        'Password' => Illuminate\Support\Facades\Password::class,
        'Queue' => Illuminate\Support\Facades\Queue::class,
        'Redirect' => Illuminate\Support\Facades\Redirect::class,
        'Redis' => Illuminate\Support\Facades\Redis::class,
        'Request' => Illuminate\Support\Facades\Request::class,
        'Response' => Illuminate\Support\Facades\Response::class,
        'Route' => Illuminate\Support\Facades\Route::class,
        'Schema' => Illuminate\Support\Facades\Schema::class,
        'Session' => Illuminate\Support\Facades\Session::class,
        'Storage' => Illuminate\Support\Facades\Storage::class,
        'URL' => Illuminate\Support\Facades\URL::class,
        'Validator' => Illuminate\Support\Facades\Validator::class,
        'View' => Illuminate\Support\Facades\View::class,
        'Excel' => Maatwebsite\Excel\Facades\Excel::class,
        'Input' => Illuminate\Support\Facades\Input::class,
        'EasyWeChat' => Overtrue\LaravelWeChat\Facade::class,
        'Image' => Intervention\Image\Facades\Image::class,
        'QrCode' => SimpleSoftwareIO\QrCode\Facades\QrCode::class,
    ],

2 Facade的底层细节

这里以缓存类的Facade为例来看下底层细节。找到Illuminate\Support\Facades\Cache类文件。

<?php

namespace Illuminate\Support\Facades;

/**
 * @method static \Illuminate\Contracts\Cache\Repository  store(string|null $name = null)
 * @method static bool has(string $key)
 * @method static mixed get(string $key, mixed $default = null)
 * @method static mixed pull(string $key, mixed $default = null)
 * @method static void put(string $key, $value, \DateTimeInterface|\DateInterval|float|int $minutes)
 * @method static bool add(string $key, $value, \DateTimeInterface|\DateInterval|float|int $minutes)
 * @method static int|bool increment(string $key, $value = 1)
 * @method static int|bool decrement(string $key, $value = 1)
 * @method static void forever(string $key, $value)
 * @method static mixed remember(string $key, \DateTimeInterface|\DateInterval|float|int $minutes, \Closure $callback)
 * @method static mixed sear(string $key, \Closure $callback)
 * @method static mixed rememberForever(string $key, \Closure $callback)
 * @method static bool forget(string $key)
 * @method static \Illuminate\Contracts\Cache\Store getStore()
 *
 * @see \Illuminate\Cache\CacheManager
 * @see \Illuminate\Cache\Repository
 */
class Cache extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'cache';
    }
}

Cache类继承了抽象类:Illuminate\Support\Facades\Facade,并重写了getFacadeAccessor方法,重写的方法只是简单的返回字符串“cache”,并没有做其他事情,更多的事情交给了父类定义的方法来做。看下抽象类Facade是怎样实现的:

<?php

namespace Illuminate\Support\Facades;

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;

    /**
     * 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调用静态方法的时候,方法不存在,转而调用PHP的魔术方法__callStatic,传入方法名和参数,在这个魔术方法中获得一个$instance变量,这个变量就是实际执行该方法的对象。这个对象是怎么来的?顺着getFacadeRoot方法,可以看到这个对象是从服务容器:Illuminate\Foundation\Application对象中解析出来的。Laravel使用这个服务容器作为联结各种服务的“胶水”或者说是容器。我们调用辅助函数dd(),打印一下这个对象:

dd(app());

在输出中找到abstractAliases和instances属性,这时instances数组里有31个类实例。abstractAliases数组里可以看到“cache”字符串映射的类:

  #instances: array:31 [▶]
  #aliases: array:66 [▶]
  #abstractAliases: array:35 [▼
    "app" => array:4 [▶]
    "auth" => array:2 [▶]
    "auth.driver" => array:1 [▶]
    "blade.compiler" => array:1 [▶]
    "cache" => array:2 [▼
      0 => "Illuminate\Cache\CacheManager"
      1 => "Illuminate\Contracts\Cache\Factory"
    ]

然后调用下Cache门面后打印:

\Cache::get(“name”);
dd(app());

  #instances: array:32 [▶]
  #aliases: array:66 [▶]
  #abstractAliases: array:35 [▼
    "app" => array:4 [▶]
    "auth" => array:2 [▶]
    "auth.driver" => array:1 [▶]
    "blade.compiler" => array:1 [▶]
    "cache" => array:2 [▼
      0 => "Illuminate\Cache\CacheManager"
      1 => "Illuminate\Contracts\Cache\Factory"
    ]

这个时候我们发现instances(在容器类中,instances属性保存的是共享实例)属性多了一个值,这个值就是实现缓存功能的对象,即Illuminate\Cache\CacheManager类的实例。

“cache” => CacheManager {#266 ▼
#app: Application {#2}
#stores: array:1 [▶]
#customCreators: []
}

总结:看到这里就明白了,缓存的Facade,Cache类的实现只是告诉服务容器,我需要的服务名字叫“cache”,你把之前绑定的类实例化后返回给我用。至于“cache”服务是何时绑定到容器中的,这就涉及框架的启动过程以及Application类的内部实现了。

上一篇:java – Facade模式和Managers类


下一篇:php – Laravel:外观实际上是在调用方法上创建新对象吗?