聊一聊Laravel的核心概念----Facades

在《简单理解Laravel的核心概念----服务容器、服务提供者、契约》中,对于Facades,我们说它是一组静态接口或者代理,作用是方便开发者简单的使用绑定到容器中的各种服务,还是那个例子:

我们经常用的Route就是一个Facade, 它是\Illuminate\Support\Facades\Route类的别名,这个Facade类代理的是注册到服务容器里的router服务,所以通过Route类我们就能够方便地使用router服务中提供的各种服务,而其中涉及到的服务解析完全是隐式地由Laravel完成的,这在一定程度上让应用程序代码变的简洁了不少.

那么Facades从被注册进laravel到被应用程序使用,这一过程是怎样一个流程呢?Facades又是如何隐式的实现服务解析呢?

注册Facades

如果深入了解过laravel的请求周期,那么一定知道Facades的注册阶段是在请求周期的Bootstrap阶段了,在请求通过中间件和路由之前,一定有一个启动应用程序的过程:具体的代码:

//  class:\Illuminate\Foundation\Http\Kernel

    /**
     * Send the given request through the middleware / router.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance('request', $request);

        Facade::clearResolvedInstance('request');

        $this->bootstrap();

        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

    /**
     * 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,
    ];

在应用启动过程中的\Illuminate\Foundation\Bootstrap\RegisterFacades::class阶段会注册应用程序里用到的所有Facades,我们可以打开这个RegisterFacades类查看一下:

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

里面只有一个bootstrap方法,这个方法的作用是通过AliasLoader类为所有的Facades注册别名,而Facades和别名的对应关系存放在config/app.php文件的$aliases数组中。

    'aliases' => [

        'App' => Illuminate\Support\Facades\App::class,
        'Arr' => Illuminate\Support\Arr::class,
        'Artisan' => Illuminate\Support\Facades\Artisan::class,
        'Route' => Illuminate\Support\Facades\Route::class,
		...
		...
    ],

我们再来看一下AliasLoader类是如何注册别名的。


//  class: Illuminate\Foundation\AliasLoader

    /**
     * Get or create the singleton alias loader instance.
     *
     * @param  array  $aliases
     * @return \Illuminate\Foundation\AliasLoader
     */
    public static function getInstance(array $aliases = [])
    {
        if (is_null(static::$instance)) {
            return static::$instance = new static($aliases);
        }

        $aliases = array_merge(static::$instance->getAliases(), $aliases);

        static::$instance->setAliases($aliases);

        return static::$instance;
    }

    /**
     * Register the loader on the auto-loader stack.
     *
     * @return void
     */
    public function register()
    {
        if (! $this->registered) {
            $this->prependToLoaderStack();

            $this->registered = true;
        }
    }

    /**
     * Prepend the load method to the auto-loader stack.
     *
     * @return void
     */
    protected function prependToLoaderStack()
    {
        spl_autoload_register([$this, 'load'], true, true);
    }

简单解释一下上面的代码:通过getInstance方法设置应用中几乎所有的门面,并通过$aliases进行缓存。设置成功后调用register方法对其进行别名绑定,而在register方法中的$registered状态记录的是是否已经完成了别名的绑定。如果没有进行别名的绑定,那么会调用prependToLoaderStack方法,将load方法注册到了SPL __autoload函数队列的头部
我们再来看看load方法:

//  class: Illuminate\Foundation\AliasLoader

    /**
     * Load a class alias if it is registered.
     *
     * @param  string  $alias
     * @return bool|null
     */
    public function load($alias)
    {
        if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) {
            $this->loadFacade($alias);

            return true;
        }

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

在load方法里$aliases配置里的Facade类创建了对应的别名,比如当我们使用别名类Route时,PHP会通过AliasLoader的load方法为把Illuminate\Support\Facades\Route::class类创建一个别名类Route,所以我们在程序里使用别Route其实使用的就是Illuminate\Support\Facades\Route类。其中的class_alias方法就是PHP对类创建别名的方法。

到此就完成了注册Facades的阶段

解析Facades代理的服务

将Facades注册到Laravel中就可以在应用程序中使用Facades了,还是以Route进行设问,如果现在存在一个路由:

Route::get('/index', 'Controller@index);

那么如何将Route解析到路由服务的呢?这就扯出来了Facades对服务的隐式解析了
我们可以看一看Route的源码:


namespace Illuminate\Support\Facades;

/**
 *
 * @see \Illuminate\Routing\Router
 */
class Route extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'router';
    }
}

奇怪的是,在Route类中并没有定义哪些get、post、put、any等方法,在父类Facade中也没有这些方法,但是我们知道调用类不存在的静态方法时会触发PHP的__callStatic静态方法。


//   class Illuminate\Support\Facades\Facade

    /**
     * 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);
    }

    /**
     * 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  object|string  $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];
    }

通过在子类Route Facade里设置的accessor(字符串’router’), 从服务容器中解析出对应的服务,router服务是在应用程序初始化时的registerBaseServiceProviders阶段(具体可以看Application的构造方法)被\Illuminate\Routing\RoutingServiceProvider注册到服务容器里的:

class RoutingServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerRouter();
        ......
    }

    /**
     * Register the router instance.
     *
     * @return void
     */
    protected function registerRouter()
    {
        $this->app->singleton('router', function ($app) {
            return new Router($app['events'], $app);
        });
    }
    ......
}

router服务对应的类就是\Illuminate\Routing\Router, 所以Route Facade实际上代理的就是这个类,Route::get实际上调用的是\Illuminate\Routing\Router对象的get方法

/**
 * Register a new GET route with the router.
 *
 * @param  string  $uri
 * @param  \Closure|array|string|null  $action
 * @return \Illuminate\Routing\Route
 */
public function get($uri, $action = null)
{
    return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}

参考:
https://laravelacademy.org/post/19439.html
https://segmentfault.com/a/1190000013969013

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值