【Laravel-海贼王系列】第十二章,Facade 模式解析

953 阅读1分钟

Facade 优雅的设计模式

我们经常这样使用一些类

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Log;

class IndexController extends Controller
{
    public function index()
    {
        Log::info('hahaha~');
    }
}

注册别名

先看对应的 Log 类在框架中注册的部分,在 app.php 文件中的别名数组

 'aliases' => [
        ...
        'Log' => Illuminate\Support\Facades\Log::class,
        ...
    ],

所以调用的时候实际容器会去解析

lluminate\Support\Facades\Log::class 这个类

我们来看这个类

<?php

namespace Illuminate\Support\Facades;

class Log extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'log';
    }
}

调用分析

最开始我们的调用方法是 Log::info 这里要追踪到父类 Facade 里面

方便阅读精简一些方法

<?php

namespace Illuminate\Support\Facades;

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

abstract class Facade
{
    protected static $app;

    protected static $resolvedInstance;
    
    ...
    
    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);
    }
}

这里的调用逻辑是通过 __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);
    }

这里也就解释了为什么我们能以静态调用的方式调用对应的方法。


开始分析

$instance = static::getFacadeRoot();

看样子是要解析一个实例出来,这里要注意的是

static 调用会指向调用的类,其次才是继承的类。

public static function getFacadeRoot()
{
    return static::resolveFacadeInstance(static::getFacadeAccessor());
}

所以这里的 static::getFacadeAccessor() 实际指向 Log

protected static function getFacadeAccessor()
{
    return 'log';
}

原来是获取一个别名,那么推测后面就是通过别名从容器拿对象了

继续看看如何拿对象

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[$name] 来获取对象

拓展- $app 来自哪里?

但是疑问来了,$app 如果是 Application 对象的话又是在什么地方赋值?

回到内核 Kernel 来看看解答,如何启动请回顾 【Laravel-海贼王系列】第四章,Kernel 类解析 handle 方法。

protected $bootstrappers = [
        ...
        \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
        ...
    ];

直接上代码

<?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
{
    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();
    }
}

真香~, Facade::setFacadeApplication($app); 在这儿传入的 Application

这里又涉及到了一些问题,那我怎么知道 $app['log'] 里面的对象是谁呢?

拓展-容器的绑定和解析!

哈哈这里去看【Laravel-海贼王系列】第三章,Container 类解析

里面的 bind() 方法就是在容器中绑定抽象和实现的功能,

这里也是容器的知识。 这里的 logApplication 在启动的时候提前以及帮我们搞定了~

位于 Application__construct()

public function __construct($basePath = null)
{
    ...
    $this->registerBaseServiceProviders();
    ...
}
protected function registerBaseServiceProviders()
{
    ...
    $this->register(new LogServiceProvider($this));
    ...
}
<?php

namespace Illuminate\Log;

use Illuminate\Support\ServiceProvider;

class LogServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton('log', function () {
            return new LogManager($this->app);
        });
    }
}

最后就看到了真正的对象 return new LogManager($this->app);

收尾

对象在手,就可以任意的调用存在的方法。

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