Published on

Laravel-Facade实现原理

Authors
  • avatar
    Name
    Xiangxuan Liu
    Twitter

在 Laravel 中,我们经常这样调用系统核心类

App::make('Test');

你可能会认为 make()App 对象的静态方法,可当你翻完整个框架后,却连 \App 类的源文件都找不到。 实际上 make() 是属于 Illuminate\Foundation\Application 的方法。在 vendor/laravel/framework/src/Illuminate/Foundation/Application.php 文件 444 行左右你可以看到 make 的申明:

public function make($abstract, $parameters = array())
{
    $abstract = $this->getAlias($abstract);

    if (isset($this->deferredServices[$abstract]))
    {
        $this->loadDeferredProvider($abstract);
    }

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

既然 App 类并不存在,为何调用其静态方法时会调用到 Application 对象同名的非静态方法呢? 下面我们来读一读源代码~

首先我们找到配置文件 app/config/app.php,在这个文件最后配置了 aliases 数组。

'aliases' => array(
    'App'               => 'Illuminate\Support\Facades\App',
    'Artisan'           => 'Illuminate\Support\Facades\Artisan',
    'Auth'              => 'Illuminate\Support\Facades\Auth',
    'Blade'             => 'Illuminate\Support\Facades\Blade',
    'Cache'             => 'Illuminate\Support\Facades\Cache',
    'ClassLoader'       => 'Illuminate\Support\ClassLoader',
    'Config'            => 'Illuminate\Support\Facades\Config',
    'Controller'        => 'Illuminate\Routing\Controller',
    'Cookie'            => 'Illuminate\Support\Facades\Cookie',
    'Crypt'             => 'Illuminate\Support\Facades\Crypt',
    'DB'                => 'Illuminate\Support\Facades\DB',
    'Eloquent'          => 'Illuminate\Database\Eloquent\Model',
    'Event'             => 'Illuminate\Support\Facades\Event',
    'File'              => 'Illuminate\Support\Facades\File',
    'Form'              => 'Illuminate\Support\Facades\Form',
    'Hash'              => 'Illuminate\Support\Facades\Hash',
    'HTML'              => 'Illuminate\Support\Facades\HTML',
    'Input'             => 'Illuminate\Support\Facades\Input',
    'Lang'              => 'Illuminate\Support\Facades\Lang',
    'Log'               => 'Illuminate\Support\Facades\Log',
    'Mail'              => 'Illuminate\Support\Facades\Mail',
    'Paginator'         => 'Illuminate\Support\Facades\Paginator',
    'Password'          => 'Illuminate\Support\Facades\Password',
    'Queue'             => 'Illuminate\Support\Facades\Queue',
    'Redirect'          => 'Illuminate\Support\Facades\Redirect',
    'Redis'             => 'Illuminate\Support\Facades\Redis',
    'Request'           => 'Illuminate\Support\Facades\Request',
    'Response'          => 'Illuminate\Support\Facades\Response',
    'Route'             => 'Illuminate\Support\Facades\Route',
    'Schema'            => 'Illuminate\Support\Facades\Schema',
    'Seeder'            => 'Illuminate\Database\Seeder',
    'Session'           => 'Illuminate\Support\Facades\Session',
    'SoftDeletingTrait' => 'Illuminate\Database\Eloquent\SoftDeletingTrait',
    'SSH'               => 'Illuminate\Support\Facades\SSH',
    'Str'               => 'Illuminate\Support\Str',
    'URL'               => 'Illuminate\Support\Facades\URL',
    'Validator'         => 'Illuminate\Support\Facades\Validator',
    'View'              => 'Illuminate\Support\Facades\View',
),

我们可以清楚的看到类别名设置关系。

vendor/laravel/framework/src/Illuminate/Foundation/start.php 文件第 180 行左右开始类别名注册。

$aliases = $config['aliases'];

AliasLoader::getInstance($aliases)->register();

经过一堆调用之后。会调用到 Illuminate\Foundation\AliasLoader的load() 方法

public function load($alias)
{
    if (isset($this->aliases[$alias]))
    {
        return class_alias($this->aliases[$alias], $alias);
    }
}

最后调用的是系统的 class_alias() 方法,不清楚的自己查查手册咯~

好的,我们现在以 App 对象举例,经过类别名之后,我们调用到了 Illuminate\Support\Facades\App,根据 PSR-4 规范,我们找到 vendor/laravel/framework/src/Illuminate/Support/Facades/App.php 文件,下面是它的全部源码。

<?php namespace Illuminate\Support\Facades;

/**
 * @see \Illuminate\Foundation\Application
 */
class App extends Facade {

    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor() { return 'app'; }

}

它只定义了一个 getFacadeAccessor() 的静态方法,返回字符串 app。 我们再来看看这个类的父类 Illuminate\Support\Facades\Facade

发现其中定义了一个 __callStatic() 的魔术方法,不清楚的童鞋再去查查手册咯~

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

    switch (count($args))
    {
        case 0:
            return $instance->$method();

        case 1:
            return $instance->$method($args[0]);

        case 2:
            return $instance->$method($args[0], $args[1]);

        case 3:
            return $instance->$method($args[0], $args[1], $args[2]);

        case 4:
            return $instance->$method($args[0], $args[1], $args[2], $args[3]);

        default:
            return call_user_func_array(array($instance, $method), $args);
    }
}

发现他获取了一个 $instance 实例变量,现在我们在看看 $instance 变量到底是什么~ 找到 getFacadeRoot() 方法

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

他调用了 getFacadeAccessor() 方法的返回值传给了 resolveFacadeInstance()。刚我们已经看了在 Illuminate\Support\Facades\App 中的 getFacadeAccessor() 返回字符串 app

再继续看 resolveFacadeInstance() 方法。里面的代码相当于回从 IOC 容器中使用 app 这个 key 值取回对应的对象。 而 appvendor/laravel/framework/src/Illuminate/Foundation/start.php 文件的 62 行左右已经注入到 IoC 容器

$app->instance('app', $app);

$app 是在更前面的加载中实例化的,就是 Application 的对象实例。

再回到 Facade__callStatic 方法,现在我们确定 $instance 就是 Application 对象的实例。

那么根据 __callStatic 的调用方式,App::make() 自然就调用 $inctance 实例的 make 方法。

边读代码边写的,可能写得有点乱,但还是希望这篇文章对你有帮助 ^_^