闭包学习 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
为truerequire_once __DIR__ . '/autoload_static.php'
为引入类ComposerStaticInitxxx,此类中有6个public static 属性,1个public static方法。- 接下来,可以看到使用
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 协议》,转载必须注明作者和本文链接