一步一步帶你實(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)載。