zoukankan      html  css  js  c++  java
  • 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使用,更建议使用容器直接解析,当然硬编码可能更适合追求速度的开发,不管怎样开心撸码最重要。

    今天没有下集预告

  • 相关阅读:
    IPFS的配置安装
    NEO VM原理及其实现(转载)
    基于NEO的私链(Private Blockchain)
    用 C# 编写 NEO 智能合约
    undefined is not an object (evaluating '_react2.PropTypes.string')
    React-native-camera error with Expo: undefined is not an object (evaluating 'CameraManager.Aspect')
    React Natived打包报错java.io.IOException: Could not delete path '...androidsupportv7'解决
    React native Configuration with name 'default' not found.
    exe程序嵌入Winform窗体
    HDOJ-2006求奇数的乘积
  • 原文地址:https://www.cnblogs.com/alwayslinger/p/13426813.html
Copyright © 2011-2022 走看看