2019-12-19 Lumen源碼分析之 一步一步帶你實(shí)現(xiàn)Lumen容器(一)

一步一步帶你實(shí)現(xiàn)Lumen容器

Ioc依賴反轉(zhuǎn)

Laravel Container 其實(shí)就是一個(gè)Ioc容器叫倍,不過人家實(shí)現(xiàn)的更加靈活更加優(yōu)雅。

簡(jiǎn)單的依賴反轉(zhuǎn)

下面我們自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單Ioc容器

class Ioc {

    private $set = [];
    private $parsed = [];

    public function set ($name , $value) {
        $this->set[$name] = $value;
    }

    public function get($name) {
        #如果根本沒設(shè)置就說明根本沒有這個(gè)類
        if(!isset($this->set[$name])) {
            return false;
        }

        # 如果沒有解析過那就解析一下
        if(empty($this->parsed[$name])) {
            $this->parsed[$name] = $this->set[$name]();
        }
        
        return $this->parsed[$name];
    }
}


$ioc = new Ioc();
$ioc->set('redis' , function(){
    $redis = new Redis();
    $redis->connect();
    return  $redis;
});

$ioc->set('mysql-master', function(){
    $master = new MySQL();
    $master->connect();
    return $master;
});

$ioc->get('redis'); // Redis Instance
$ioc->get('mysql-master'); // MySQL Instance

這就是Ioc,很好理解,如果不理解可以自己多敲幾遍。 接下來繼續(xù)

給自己的Ioc容器提一下B格

如果第一次看Laravel 兆旬、Lumen的容器源碼可能會(huì)被里面的變量名搞的比較迷糊、畢竟很多人英文底子不是很好怎栽。
我也有這樣的問題T-T丽猬、為了解決這個(gè)問題咱們簡(jiǎn)單裝飾一下剛才的簡(jiǎn)單容器、可以初步了解lumen的容器中幾個(gè)屬性名的含義熏瞄。

class Container {
    private $bindings = [];

    private $instances = [];

    public function bind($abstract , $concrete) {
        $this->bindings[$abstract] = $concrete;
    }

    public function make($abstract) {
        if (!isset($this->bindings[$abstract])) {
            return false;
        }

        if (empty($this->instances[$abstract])) {
            $this->instances[$abstract] = $this->bindings[$abstract]();
        }

        return $this->instances[$abstract];
    }
}
$container = new Container();

$container->bind('redis' , function(){
    $redis = new Redis();
    $redis->connect();
    return  $redis;
});

$container->bind('mysql-master' , function(){
    $master = new MySQL();
    $master->connect();
    return $master;
});

$container->make('redis'); //Redis Instance;
$container->make('mysql-master'); //MySQL Instance;

是不是滿滿的逼格脚祟?
任何項(xiàng)目或者事情都是先做出來一個(gè)小雛形,在其基礎(chǔ)上針對(duì)已有的問題做優(yōu)化、升級(jí);
那么我們現(xiàn)在就開始找問題,并且升級(jí)吧失球。帶你一步步實(shí)現(xiàn)lumen容器。

問題一行您,如果想通過同一個(gè)類名,獲取不同的對(duì)象剪廉,而不是單例對(duì)象怎么辦娃循?

class Container {
    private $bindings = [];

    private $instances = [];

    public function bind($abstract , $concrete) {
        $this->bindings[$abstract] = $concrete;
    }

    public function make($abstract) {
        if (!isset($this->bindings[$abstract])) {
            return false;
        }

        if (empty($this->instances[$abstract])) {
            $this->instances[$abstract] = $this->bindings[$abstract]();
        }

        return $this->instances[$abstract];
    }
}

class Person{
    public $name;
}

$container = new Container();
$container->bind('person' , function(){
    return new Person();
});

$lilei = $container->make('person');
$lilei->name = 'lilei';
$liming = $container->make('person');
$liming->name = 'liming';

echo $lilei->name , PHP_EOL;
echo $liming->name, PHP_EOL;
// 運(yùn)行結(jié)果
// liming
// liming
// 并不是我們想要的結(jié)果

lumen的做法很簡(jiǎn)單就是在bind的時(shí)候加一個(gè)shared參數(shù),用來告訴容器這個(gè)類是否共享的(單例)我們來解決這個(gè)問題

class Container {

    private $bindings = [];

    private $instances = [];

    #添加 $shared 參數(shù) 
    public function bind($abstract , $concrete, $shared = false) {
        #修改值為一個(gè)數(shù)組類型
        $this->bindings[$abstract] = [
            'concrete' => $concrete,
            'shared' => $shared
        ];
    }

    public function make($abstract) {
        if (!isset($this->bindings[$abstract])) {
            return false;
        }

        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        # 通過數(shù)組concrete字段執(zhí)行閉包
        $object = $this->bindings[$abstract]['concrete']();
        # 判斷是否需要單例模式
        if($this->bindings[$abstract]['shared']) {
            $this->instances[$abstract] = $object;
        }

        return $object;
    }
}
class Person{
    public $name;
}

$container = new Container();
$container->bind('person' , function(){
    return new Person();
},true);

$lilei = $container->make('person');
$lilei->name = 'lilei';
$liming = $container->make('person');
$liming->name = 'liming';

echo $lilei->name , PHP_EOL;
echo $liming->name, PHP_EOL;
// 運(yùn)行結(jié)果
// lilei
// liming
// 想要的結(jié)果出現(xiàn)了

問題二妈经,這個(gè)其實(shí)也不算問題淮野,就是寫法上的優(yōu)化,我們是不是每次都要傳遞第二個(gè)參數(shù)并且每次都要寫一個(gè)閉包進(jìn)去捧书?

# class Container# 復(fù)制上面的代碼

class Person{
    public $name;
}

$container = new Container();
$container->bind('person' , function(){
    return new Person();
},true);

我們來分析一下吹泡,實(shí)際上我已經(jīng)知道了Person類,那么我們能不能想下面代碼中的這么實(shí)現(xiàn)呢

...
$container = new Container();
$container->bind('Person' , 'Person' , true);
$container->bind('Person');
...

接下來繼續(xù)改進(jìn)

class Container {

    private $bindings = [];

    private $instances = [];

    #這里是生成閉包的地方
    public function getClosure($concrete) {
        return function() use($concrete) {
            return new $concrete;
        };
    }


    public function bind($abstract , $concrete=null, $shared = false) 
    {
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        # 如果$concrete 不是一個(gè)閉包生成一個(gè)閉包
        if (!$concrete instanceof Closure) {
            $concrete = $this->getClosure($concrete);
        }

        $this->bindings[$abstract] = [
            'concrete' => $concrete,
            'shared' => $shared
        ];
    }

    public function make($abstract) {
        if (!isset($this->bindings[$abstract])) {
            return false;
        }

        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        $object = $this->bindings[$abstract]['concrete']();
        if($this->bindings[$abstract]['shared']) {
            $this->instances[$abstract] = $object;
        }

        return $object;
    }
}

class Person{
    public $name;
}

# testing
$container = new Container();
$container->bind('Person' , 'Person' , false);
$p1 = $container->make('Person');
$p2 = $container->make('Person');
var_dump($p1 , $p2 , $p1 === $p2);

$c1 = new Container();
$c1->bind('Person');
$p3 = $c1->make('Person');
$p4 = $c1->make('Person');
var_dump($p3 , $p4 , $p3 === $p4);


$c2 = new Container();
$c2->bind('Person','Person', true);
$p5 = $c2->make('Person');
$p6 = $c2->make('Person');
var_dump($p5 , $p6 , $p5 === $p6);

入門我們自己寫這個(gè)container大部門人會(huì)在make里去判斷是否是一個(gè)閉包经瓷,寫完就不再優(yōu)化了...

問題三爆哑,如果我們定義類構(gòu)造函數(shù)的時(shí)候依賴其他的參數(shù)怎么辦?

# class Container# 復(fù)制上面的代碼

# testing
class Person{
    private $name;
    private $isProgrammer;
    public function __construct($name,$isProgrammer = true) {
        $this->name = $name;
        $this->isProgrammer = $isProgrammer;
    }

    public function me() {
        $message = $this->isProgrammer ? ',Ta是一個(gè)程序員' :'';
        return "姓名: {$this->name} $message";
    }
}
$container = new Container();
$container->bind('Person');
$p1 = $container->make('Person',[
    'name' => 'lilei',
]);

echo $p1->me();

# 這樣看肯定是不行的舆吮,會(huì)報(bào)出錯(cuò)誤

接下來繼續(xù)解決問題

class Container {

    private $bindings = [];

    private $instances = [];

    public function getClosure($concrete) {
        # 讓閉包帶參
        return function($parameter = []) use($concrete) {
            #將參數(shù)傳遞給具體的類揭朝、就算構(gòu)造函數(shù)不需要參數(shù)這樣寫也不會(huì)有任何問題
            return new $concrete($parameter);
        };
    }


    public function bind($abstract , $concrete=null, $shared = false) 
    {
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        if (!$concrete instanceof Closure) {
            $concrete = $this->getClosure($concrete);
        }

        $this->bindings[$abstract] = [
            'concrete' => $concrete,
            'shared' => $shared
        ];
    }

    # 在這里添加一個(gè)$paramters 參數(shù)
    public function make($abstract ,array $parameters = []) {
        if (!isset($this->bindings[$abstract])) {
            return false;
        }

        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        # 那么其實(shí)在這里處理一下就可以了
        # 需不需要參數(shù)队贱?到底需不需要參數(shù)我們不知道
        # 因?yàn)?this->bindings[$abstract]['concrete'] 是一個(gè)閉包函數(shù)        
        $concrete = $this->bindings[$abstract]['concrete'];
        #$concrete($parameters) 相當(dāng)于使用getClosure中閉包函數(shù);
        # $concrete =  function() use($concrete1) {
        //  return new $concrete1;
        // };
        # 如果想傳遞一個(gè)參數(shù)給閉包那么應(yīng)該修改一下getClosure方法,讓閉包方法帶參
        $object = $concrete($parameters);
        if($this->bindings[$abstract]['shared']) {
            $this->instances[$abstract] = $object;
        }

        return $object;
    }
}

#testing
class Person{
    private $name;
    public function __construct($param) {
        $this->name = $param['name'] ?? 'unknown';
    }

    public function getName() {
        return $this->name;
    }
}

雖然看起來解決了帶參問題潭袱,但是問題很明顯柱嫌。我們給構(gòu)造函數(shù)傳遞的參數(shù)是一個(gè)數(shù)組,我們無法做到讓每個(gè)人寫的插件都把構(gòu)造函數(shù)寫成這樣屯换。透明性并不好编丘。

問題四、我們現(xiàn)在遇到的問題是構(gòu)建對(duì)象的時(shí)候需要明確構(gòu)造函數(shù)的參數(shù)類型彤悔、數(shù)量嘉抓。并且增加程序的透明度

# class Container# 復(fù)制上面的代碼
class Person{
    private $name;
    public function __construct($param) {
        $this->name = $param['name'] ?? 'unknown';
    }

    public function getName() {
        return $this->name;
    }
}

我們上一個(gè)版本的解決方案顯然不是很理想。我們不能要求每個(gè)類的構(gòu)造函數(shù)都傳一個(gè)$param數(shù)組晕窑,這樣不透明也不現(xiàn)實(shí)
那么如果我們要這樣使用一個(gè)容器呢抑片?

class Person{
    private $name;
    private $isProgrammer;
    public function __construct($name,$isProgrammer = true) {
        $this->name = $name;
        $this->isProgrammer = $isProgrammer;
    }

    public function me() {
        $message = $this->isProgrammer ? ',Ta是一個(gè)程序員' :'';
        return "姓名: {$this->name} $message";
    }
}
$container = new Container();
$container->bind('Person');
$p1 = $container->make('Person',[
    'name' => 'lilei',
]);

echo $p1->me();

接下來我們來解決上述問題

class Container {

    private $bindings = [];
    private $instances = [];

    public function getClosure($concrete) {
        return function($parameter = []) use($concrete) {
            # 在這里我們找到了判斷初始化函數(shù)的契機(jī),利用反射我們可以做很多事杨赤,包括我們的問題
            # 1.獲得一個(gè)$concrete類反射
            $reflector = new ReflectionClass($concrete);
            # 2.判斷這個(gè)類能否被實(shí)例化敞斋,例如 private function __construct(){}
            if(!$reflector->isInstantiable()) {
                throw new Exception("{$concrete} 無法被實(shí)例化");
            }

            # 3.獲取構(gòu)造函數(shù)反射方法
            $constructor = $reflector->getConstructor();

            # 4.獲取參數(shù)列表
            $parameters = $constructor->getParameters();

            # 5.遍歷參數(shù)列表
            $instances = [];
            foreach ($parameters as $_parameter) {
                # 如果已經(jīng)$parameter中已經(jīng)設(shè)置了對(duì)應(yīng)的參數(shù)
                if(isset($parameter[$_parameter->name])) {
                    $instances[] = $parameter[$_parameter->name];
                    continue;
                }
                # 如果沒設(shè)置判斷一下這個(gè)參數(shù)是否存在默認(rèn)值
                if(!$_parameter->isDefaultValueAvailable()) {
                    throw new Exception("{$concrete} 無法被實(shí)例化,缺少參數(shù){$_parameter->name}");
                }

                $instances[] = $_parameter->getDefaultValue();
            }

            # 這里就需要通過反射來構(gòu)建對(duì)象了
//            return new $concrete($parameter);
            return $reflector->newInstanceArgs($instances);
        };
    }


    public function bind($abstract , $concrete=null, $shared = false)
    {
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        if (!$concrete instanceof Closure) {
            $concrete = $this->getClosure($concrete);
        }

        $this->bindings[$abstract] = [
            'concrete' => $concrete,
            'shared' => $shared
        ];
    }

    public function make($abstract ,array $parameters = []) {
        if (!isset($this->bindings[$abstract])) {
            return false;
        }

        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        # 先獲取到具體的類型
        $concrete = $this->bindings[$abstract]['concrete'];
        # 這里需要思考一下
        # 到目前為止我們的$this->bindings[$abstract]['concrete']里存儲(chǔ)的都是通過getClosure方法生成的閉包。
        # 那么直接在這里判斷類型肯定行不通,所以我們跳到getClosure里面去看看
        $object = $concrete($parameters);
        if($this->bindings[$abstract]['shared']) {
            $this->instances[$abstract] = $object;
        }

        return $object;
    }
}

class Person{
    private $name;
    private $isProgrammer;
    public function __construct($name,$isProgrammer = true) {
        $this->name = $name;
        $this->isProgrammer = $isProgrammer;
    }

    public function me() {
        $message = $this->isProgrammer ? ',Ta是一個(gè)程序員' :'';
        return "姓名: {$this->name} $message";
    }
}
$container = new Container();
$container->bind('Person');
$p1 = $container->make('Person',[
    'name' => 'lilei',
]);

echo $p1->me();

先到這里疾牲,休息一下

本系列一律手寫渺尘,未經(jīng)允許謝絕轉(zhuǎn)載。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末说敏,一起剝皮案震驚了整個(gè)濱河市鸥跟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盔沫,老刑警劉巖医咨,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異架诞,居然都是意外死亡拟淮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門谴忧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來很泊,“玉大人,你說我怎么就攤上這事沾谓∥欤” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵均驶,是天一觀的道長(zhǎng)昏兆。 經(jīng)常有香客問我,道長(zhǎng)妇穴,這世上最難降的妖魔是什么爬虱? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任隶债,我火速辦了婚禮,結(jié)果婚禮上跑筝,老公的妹妹穿的比我還像新娘死讹。我一直安慰自己,他們只是感情好曲梗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布回俐。 她就那樣靜靜地躺著,像睡著了一般稀并。 火紅的嫁衣襯著肌膚如雪仅颇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天碘举,我揣著相機(jī)與錄音忘瓦,去河邊找鬼。 笑死引颈,一個(gè)胖子當(dāng)著我的面吹牛耕皮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蝙场,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凌停,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了售滤?” 一聲冷哼從身側(cè)響起罚拟,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎完箩,沒想到半個(gè)月后赐俗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弊知,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年阻逮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秩彤。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叔扼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出漫雷,到底是詐尸還是另有隱情瓜富,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布珊拼,位于F島的核電站食呻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏澎现。R本人自食惡果不足惜仅胞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望剑辫。 院中可真熱鬧干旧,春花似錦、人聲如沸妹蔽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胳岂。三九已至编整,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乳丰,已是汗流浹背掌测。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留产园,地道東北人汞斧。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像什燕,于是被迫代替她去往敵國(guó)和親粘勒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355