闭包学习 1--实例分析 ComposerStaticInitxx::getInitializer

laravel版本

  • 5.7.9

为什么要分析?

这段代码涉及到了闭包和闭包对象的绑定,在理解管道流时应该会用到。

代码上下文

vendor/composer/autoload_real.php第29行

if ($useStaticLoader) {
    require_once __DIR__ . '/autoload_static.php';
    call_user_func(\Composer\Autoload\ComposerStaticInitxxx::getInitializer($loader));
} else {
  • $useStaticLoader为true
  • require_once __DIR__ . '/autoload_static.php'为引入类ComposerStaticInitxxx,此类中有6个public static 属性,1个public static方法。
    file
  • 接下来,可以看到使用call_user_func去调用了用户自定义函数getInitializer,传入了一个$loader对象。
  • $loader对象在23行被创建,是一个Composer\Autoload\ClassLoader对象。
    self::$loader = $loader = new \Composer\Autoload\ClassLoader();
  • 接下来主要就是要理解getInitializer这个方法。

理解getInitializer

初次理解

  • 顾名思义,就是获得初始化的意思。
  • 代码如下:

        public static function getInitializer(ClassLoader $loader)
        {
            return \Closure::bind(function () use ($loader) {
                $loader->prefixLengthsPsr4 = ComposerStaticInit722340cdc640bb3bac7f52d67625e293::$prefixLengthsPsr4;
                $loader->prefixDirsPsr4 = ComposerStaticInit722340cdc640bb3bac7f52d67625e293::$prefixDirsPsr4;
                $loader->fallbackDirsPsr4 = ComposerStaticInit722340cdc640bb3bac7f52d67625e293::$fallbackDirsPsr4;
                $loader->prefixesPsr0 = ComposerStaticInit722340cdc640bb3bac7f52d67625e293::$prefixesPsr0;
                $loader->classMap = ComposerStaticInit722340cdc640bb3bac7f52d67625e293::$classMap;
    
            }, null, ClassLoader::class);
        }
  • 可以看到,异常复杂,返回的应该是一个类,这个类又包含了一个匿名函数,一个null,一个全类名字符串。完全无法理解。

查看文档学习

  • 只能查看官方文档: Closure::bind
  • 看完后还是不理解,发现有这么一句:“这个方法是 Closure::bindTo() 的静态版本。查看它的文档获取更多信息。”,转而查看 Closure::bindTo
  • 这次就清楚多了。

自定义例子来验证文档。

  • 在web.php中定义如下路由和内容:

    Route::get('/', function () {
        class Person
        {
            protected $has_tail = false;
        }
    
        class Chimpanzee
        {
            protected $has_tail = true;
        }
    
        $person = new Person();
        $chimpanzee = new Chimpanzee();
    
        $fuc = function (){
            dump($this);
            if (isset($this->has_tail)) {
                if ($this->has_tail) {
                    echo '我是一个闭包猩猩<br><br>';
                } elseif (!$this->has_tail) {
                    echo '我是一个闭包人<br><br>';
                }
            }
        };
    
        //创建并返回一个 匿名函数, 它与当前对象的函数体相同、绑定了同样变量
        $fuc2 = $fuc->bindTo(null);
        dump($fuc2);
        //call_user_func($fuc2); //没有绑定对象,不能在闭包中使用$this.否则会报错。
    
        //可以绑定不同的对象
        $fuc3 = $fuc->bindTo($person);
        dump($fuc3);
        call_user_func($fuc3);  //绑定了person对象,可以在闭包中使用$this。但无法使用对象的protected或private属性。
    
        //也可以绑定新的类作用域
        $fuc4 = $fuc->bindTo($person, Person::class);
        dump($fuc4);
        call_user_func($fuc4);  //绑定了对象和新的类作用域,可以在闭包中使用$this,也可以使用对象的protected或private属性。
    
        $fuc5 = $fuc->bindTo($person, Chimpanzee::class);
        dump($fuc5);
        call_user_func($fuc5);  //绑定的对象和新的类作用域,可以在闭包中使用$this,也可以使用对象的protected或private属性。但是在此例中,绑定对象和类作用域需要是一个类,不一致就无法获取到对应属性。如果不绑定对象,就没有这个限制。但是前面的例子就会出错。
    
        $fuc6 = $fuc->bindTo($chimpanzee, Chimpanzee::class);
        dump($fuc6);
        call_user_func($fuc6);  //绑定的对象和新的类作用域一致,就可以正确获取对应属性了。
    
        //接下来看看静态闭包
        $fuc_static = static function () {
            dump($this);
            if (isset($this->has_tail)) {
                if ($this->has_tail) {
                    echo '我是一个闭包猩猩<br><br>';
                } elseif (!$this->has_tail) {
                    echo '我是一个闭包人<br><br>';
                }
            }
        };
        //call_user_func($fuc_static);    //静态闭包内不能使用$this,否则会报错"Using $this when not in object context"。
    
        //重新定义一个静态闭包,使用use传入需要的对象。
        $fuc_static = static function () use($person) {
            if (isset($person->has_tail)) {
                if ($person->has_tail) {
                    echo '我是一个从外面跑进来的猩猩<br><br>';
                } elseif (!$person->has_tail) {
                    echo '我是一个从外面跑进来的人<br><br>';
                }
            }
        };
        //$fuc_static2 = $fuc_static->bindTo($chimpanzee);
        //call_user_func($fuc_static2);   //静态闭包不能有绑定对象。"Cannot bind an instance to a static closure"
    
        $fuc_static3 = $fuc_static->bindTo(null);
        call_user_func($fuc_static3); //静态闭包不能有绑定的对象( newthis 参数的值应该设为 NULL),如果不传入新的类作用域,会无法访问对象的protected或private属性。
    
        $fuc_static4 = $fuc_static->bindTo(null, Person::class);
        call_user_func($fuc_static4);  //静态闭包不能有绑定的对象( newthis 参数的值应该设为 NULL)不过仍然可以用 bindTo 方法来改变它们的类作用域。
    
    });
  • 按照上面的内容,一项项的测试就明白了,comment的语句会出错,所以comment了,可以uncomment来看原因。。噗。。。

回到getInitializer中

  • 可以看到,getInitializer就是最后一种情况,静态闭包静态闭包不能有绑定的对象( newthis 参数的值应该设为 NULL)不过仍然可以用 bindTo 方法来改变它们的类作用域。
  • 这样,就理解了为什么返回的闭包对象的第二参数为null,因为getInitializer已声明为static
  • 第三参数传ClassLoader::class的原因是要对传入的$loader对象的私有属性进行赋值操作,如果不明确类作用域,就无法赋值。
  • 赋值完成后,因为是对象引用传递,被修改后的$loader还能在此函数外继续使用。
本作品采用《CC 协议》,转载必须注明作者和本文链接
日拱一卒
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
93
粉丝
85
喜欢
152
收藏
121
排名:71
访问:11.4 万
私信
所有博文
社区赞助商