• Laravel Facade原理及使用


    Laravel Facade原理及使用

    laravel过于庞大,加之笔者水平有限,所以后面的源码解读会按模块功能介绍,希望能帮大家稍微捋顺下思路,即使能够帮助大家回顾几个函数也好。如发现错误,还望指正。

    • facade工作方式,允许我们可以通过静态调用的方式直接使用容器中的服务
    • 原理讲解,在laravel的routes/web.php等路由文件下,经常可以看到类似的写法
    <?php
    Route::get('zbc', function () {
        app()->make(AppHttpControllersbcTestController::class);
    });
    // 可以看到通过Route::get方法添加了此路由规则(不仅如此laravel存在大量这般的静态调用方式),但是并没有看到此文件中引用Route类,并且laravel框架中并没有此Route类,
    // 通过打印get_declared_classes确实存在此Route(route)类,只有一个解释,那就是laravel引导过程中‘生成了’这个类,下面就会讲解如何‘生成’的这个类,这个类是根据什么‘生成’的。
    
    • 上文讲到在index.php中拿到了laravel的’黑盒子‘$kernel,下面继续看kernel的handle方法
    // IlluminateFoundationHttpKernel文件中
    /**
     * Handle an incoming HTTP request.
     *
     * @param  IlluminateHttpRequest  $request
     * @return IlluminateHttpResponse
     */
    public function handle($request)
    {	
        // 这里的方法大家看名字就能大概知道什么用处,本问只讲解sendRequestThroughRouter中的facade注册部分
        try {
            $request->enableHttpMethodParameterOverride();
    		// 通过路由或者中间件处理给定的请求
            // 跳转到sendRequestThroughRouter方法
            $response = $this->sendRequestThroughRouter($request);
        } catch (Exception $e) {
            $this->reportException($e);
    
            $response = $this->renderException($request, $e);
        } catch (Throwable $e) {
            $this->reportException($e = new FatalThrowableError($e));
    
            $response = $this->renderException($request, $e);
        }
    
        $this->app['events']->dispatch(
            new RequestHandled($request, $response)
        );
    
        return $response;
    }
    
    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance('request', $request);
        Facade::clearResolvedInstance('request');
    
        // 引导app
        // 跳转到bootstrap方法
        $this->bootstrap();
    	
        // laravel的pipeline以后会专门讲解
        return (new Pipeline($this->app))
            ->send($request)
            ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
            ->then($this->dispatchToRouter());
    }
    
    // $this->app是make的时候通过构造方法注入进来的(参考上篇文章)
    public function bootstrap()
    {   
        if (! $this->app->hasBeenBootstrapped()) {
            // 如果laravel不能存在引导完成标志,就进行引导
            // 跳转到bootstrapWith方法,传递的参数如下
            // protected $bootstrappers = [
            //     IlluminateFoundationBootstrapLoadEnvironmentVariables::class,
            //     IlluminateFoundationBootstrapLoadConfiguration::class,
            //     IlluminateFoundationBootstrapHandleExceptions::class,
            //     我们的facade'生成'(注册)就在此完成
            //     IlluminateFoundationBootstrapRegisterFacades::class,
            //     IlluminateFoundationBootstrapRegisterProviders::class,
            //     IlluminateFoundationBootstrapBootProviders::class,
        	// ];
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }
    
    // Application下的bootstrapWith方法
    public function bootstrapWith(array $bootstrappers)
    {   
        $this->hasBeenBootstrapped = true;
    
        foreach ($bootstrappers as $bootstrapper) {
            $this['events']->dispatch('bootstrapping: ' . $bootstrapper, [$this]);
            // 本文只讲解facaderegister
            // 所以此处应该的$$bootstrapper = IlluminateFoundationBootstrapRegisterFacades
            // 如果看过前面的文章可以知道容器并没有绑定过此abstract更不可能存在解析过的instance
            // 所以容器的make方法走的一定是php的反射机制,然后调用bootstrap方法
            // 跳转到RegisterFacades的bootstrap方法
            $this->make($bootstrapper)->bootstrap($this);
    
            $this['events']->dispatch('bootstrapped: ' . $bootstrapper, [$this]);
        }
    }
    
    public function bootstrap(Application $app)
    {
        Facade::clearResolvedInstances();
    
        Facade::setFacadeApplication($app);
    	
        // 跳转到getInstance方法
        AliasLoader::getInstance(array_merge(
            // 拿到config/app.php下的aliases数组 如下
            // 'App' => IlluminateSupportFacadesApp::class,
            // 'Arr' => IlluminateSupportArr::class,
            // ...
            // 'Route' => IlluminateSupportFacadesRoute::class,
            // ...
            // 'Validator' => IlluminateSupportFacadesValidator::class,
            // 'View' => IlluminateSupportFacadesView::class,
            $app->make('config')->get('app.aliases', []),
            // 需要修改composer.json文件 配合包自动发现 这个类就是这个用处的
            $app->make(PackageManifest::class)->aliases() 
        ))->register();
    }
    
    // IlluminateFoundationAliasLoader类
    // 方法很简单 
    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方法
    /**
     * Register the loader on the auto-loader stack.
     *
     * @return void
     */
    public function register()
    {
        if (!$this->registered) {
            // 继续跳转prependToLoaderStack到方法
            $this->prependToLoaderStack();
    
            $this->registered = true;
        }
    }
    
    // 可以看到此方法在加载函数队列首部添加了一个load加载函数
    // spl_autoload_register方法的参数在composer第一篇有讲解
    protected function prependToLoaderStack()
    {	
        // 跳转到load方法
        spl_autoload_register([$this, 'load'], true, true);
    }
    
    /**
     * 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])) {
            // 重点!!!
            // 在此案例中$alias传递进来的是我们在路由文件web.php中使用的Route
            return class_alias($this->aliases[$alias], $alias);
        }
        
        // 为了方便理解,可做如下简单修改
        // config/app.php的aliases数组中存在Route映射 条件表达式为true
        if (isset($this->aliases[$alias])) {
            dump($alias);	// 打印为Route,就是我们在web.php中使用的Route
            dump(get_declared_classes()); // 一堆类名组成的数组 但是不包括route
            // 关键在此函数 class_alias 第一个参数是original class 第二个参数是给这个类起的别名
            // 最重要的第三个参数默认为true 表示如果原类没找到 是否可以通过别名自动加载这个类
            // class_alias返回bool
            // 返回的只是bool值,load加载器并没有真正的引入实际的IlluminateSupportFacadesRoute类
            // 所以php会继续调用composer的加载器真正的加载此类,此加载器只是取巧的设置了别名,方便使用
            class_alias($this->aliases[$alias], $alias);
            dump(get_declared_classes()); // 一堆类名组成的数组 但是包括了route
            return true;
        }
    }
    
    // 下面看IlluminateSupportFacadesRoute类
    // 非常简单只有一个方法,并没有发现web.php中的Route::get方法,便会触发php的魔术方法__callStatic(请在自省查阅手册),这是laravel facade静态调用实现的根本方式
    // 跳转到父类Facade
    class Route extends Facade
    {
        /**
         * Get the registered name of the component.
         *
         * @return string
         */
        protected static function getFacadeAccessor()
        {
            return 'router';
        }
    }
    
    
    // IlluminateSupportFacadesFacade类
    public static function __callStatic($method, $args)
    {	
        // 使用static关键字,实现延迟绑定,此案例中代表IlluminateSupportFacadesRoute
        // 跳转到getFacadeRoot方法
        $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()
    {   
        // 注意使用的是static 静态延迟绑定 此案例中代表IlluminateSupportFacadesRoute
        // 跳转到resolveFacadeInstance方法
        return static::resolveFacadeInstance(static::getFacadeAccessor());
        // return static::resolveFacadeInstance('router');
    }
    
    /**
     * 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];
        }
    	
        if (static::$app) {
            // $app为Application对象但是为什么采用数组的形式进行访问呢($app['router'])
            // 因为Application类继承了Container类,而Container实现了spl类库提供的ArrayAccess接口
            // 关于ArrayAccess请自行查阅文档
            // 当通过数组的形式访问对象的时候 会触发offsetGet方法,跳转到Container的offsetGet方法
            return static::$resolvedInstance[$name] = static::$app[$name];
        }
    }
    
    /**
     * Get the value at a given offset.
     *
     * @param  string  $key
     * @return mixed
     */
    public function offsetGet($key)
    {	
        // $key在此案例中等于router
        // 通过容器进行解析router
        // router在Application中实例化的registerBaseServiceProvider中实现的注册,前面的文章有讲解
        // 一路返回到__callStatic方法中
        return $this->make($key);
    }
    
    public static function __callStatic($method, $args)
    {	
        // 使用static关键字,实现延迟绑定,此案例中代表IlluminateSupportFacadesRoute
        // 跳转到getFacadeRoot方法
        $instance = static::getFacadeRoot();
    
        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }
    	// 到此调用$router的get方法 完美!!!
        // 对应的方法看这里 IlluminateRoutingRouter::get
        return $instance->$method(...$args);
    }
    

    以上便是laravel facade的基本实现方式,大家可能已经看出来这就是php实现门面(代理)模式的方法。

    个人不喜欢使用门面模式,更喜欢直接调用系统函数等方式直接从容器中解析对象,感觉可以规避一下__callStatic的调用。

    下面讲解如何在laravel中使用自己的facade

    1. 创建契约
    <?php
    
    namespace AppContracts;
    
    interface MyFacade
    {
        public function thereYouGo();
    }
    
    2. 创建服务
    <?php
    
    namespace AppServices;
    
    use AppContractsMyFacade;
    
    class TestService implements MyFacade
    {
        public function thereYouGo()
        {
            echo '春风习习可盈袖, 不及伊人半点红';
        }
    }
    
    3. 创建服务提供者 php artisan make:provider MyFacadeProvider
    <?php
    
    namespace AppProviders;
    
    use IlluminateSupportServiceProvider;
    use AppServicesTestService;
    
    class MyFacadeProvider extends ServiceProvider
    {
        /**
         * Register services.
         *
         * @return void
         */
        public function register()
        {	
            // 注意此处的$abstract(MyLove)要和facade中getFacadeAccessor方法返回值一致
            $this->app->bind('MyLove', function () {
                return new TestService();
            });
        }
    
        /**
         * Bootstrap services.
         *
         * @return void
         */
        public function boot()
        {
            //
        }
    }
    
    4. 创建门面
    <?php
    
    namespace AppFacades;
    
    use IlluminateSupportFacadesFacade;
    
    class MyFacade extends Facade
    {
        protected static function getFacadeAccessor()
        {   
            return 'MyLove';
        }
    }
    
    5. 注册服务和门面 config/app.php下添加
    'providers' => [
    	...
        AppProvidersMyFacadeProvider::class,
    ],
    'aliases' => [
        ...
        'MyFacade' => AppServicesTestService::class,
    ]
       
    6. 测试
    use AppFacadesMyFacade;
    Route::get('myfacade', function () {
        MyFacade::thereYouGo();
    });
    

    可以看到实现一个facade真的费时费力,并且性能不好,不建议自行创建facade使用,更建议使用容器直接解析,当然硬编码可能更适合追求速度的开发,不管怎样开心撸码最重要。

    今天没有下集预告

  • 相关阅读:
    第八章 多线程编程
    Linked List Cycle II
    Swap Nodes in Pairs
    Container With Most Water
    Best Time to Buy and Sell Stock III
    Best Time to Buy and Sell Stock II
    Linked List Cycle
    4Sum
    3Sum
    Integer to Roman
  • 原文地址:https://www.cnblogs.com/alwayslinger/p/13426813.html
Copyright © 2020-2023  润新知