閉包學習 1--例項分析 ComposerStaticInitxx::getInitializer

hustnzj發表於2018-11-14
  • 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這個方法。

初次理解

  • 顧名思義,就是獲得初始化的意思。
  • 程式碼如下:

        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還能在此函式外繼續使用。

相關文章