- 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
這個方法。
初次理解
- 顧名思義,就是獲得初始化的意思。
-
程式碼如下:
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還能在此函式外繼續使用。