容器实现类的实例自动绑定依靠了反射,但是5.1和6的代码,我认为还是有缺陷。话不多说直接上代码,以下是我单独做测试,将容器源码单独弄出来删除某些分支后的代码,保留了容器最基本的功能:
class Container{private static $instance; //容器实例public $instances = []; //注册池//单例模式,以静态的方式调用对象以及其方法public static function getInstance(){if (is_null(self::$instance)) {self::$instance = new static;}return self::$instance;}//注册池注册实例public function set($className, $class){$this->instances[$className] = $class;}public function get($className,$vars=[]){$this->make($className,$vars);}public function make($abstract, $vars = []){$object = $this->invokeClass($abstract, $vars);return $object;}public function invokeClass($class, $vars = []){$reflect = new \ReflectionClass($class);$constructor = $reflect->getConstructor();$args = $constructor ? $this->bindParams($constructor, $vars) : [];return $reflect->newInstanceArgs($args);}public function bindParams($reflect, $vars = []){if ($reflect->getNumberOfParameters() == 0) {return [];}//這個参数列表是必须要传入的否则,遇到递归的分析(即依赖注入的参数列表中带有对象类型参数)是绝对是报错的// 判断数组类型 数字数组时按顺序绑定参数reset($vars);$type = key($vars) === 0 ? 1 : 0;$params = $reflect->getParameters();foreach ($params as $param) {$name = $param->getName();$lowerName = self::parseName($name);$class = $param->getClass();if ($class) {$args[] = $this->getObjectParam($class->getName(), $vars);} elseif (1 == $type && !empty($vars)) {$args[] = array_shift($vars);} elseif (0 == $type && isset($vars[$name])) {$args[] = $vars[$name];} elseif (0 == $type && isset($vars[$lowerName])) {$args[] = $vars[$lowerName];} elseif ($param->isDefaultValueAvailable()) {$args[] = $param->getDefaultValue();}}return $args;}public static function parseName($name, $type = 0, $ucfirst = true){if ($type) {$name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {return strtoupper($match[1]);}, $name);return $ucfirst ? ucfirst($name) : lcfirst($name);}return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));}public function getObjectParam($className, &$vars){$array = $vars;$value = array_shift($array);if ($value instanceof $className) {$result = $value;array_shift($vars);} else {$result = $this->make($className);}return $result;}}
调用的代码:
spl_autoload_register('autoLoading');
function autoLoading()
{include './Car.php';
}
//echo person::class;
Container::getInstance()->get('person',[1,2]);
依赖注入的类:
class person {public function __construct(Car $obj,$a,$b){$sub = $this->add($a,$b);$obj->speed($sub);}public function add($a,$b) {return $a+$b;}
}class Car{public function __construct(){$app->show();}public function speed($sub) {echo '我的马力很猛,高达:'.$sub;}
}
缺陷:
1.参数分析不是非常合理,请看这段代码:
reset($vars);$type = key($vars) === 0 ? 1 : 0;$params = $reflect->getParameters();foreach ($params as $param) {$name = $param->getName();$lowerName = self::parseName($name);$class = $param->getClass();if ($class) {$args[] = $this->getObjectParam($class->getName(), $vars);} elseif (1 == $type && !empty($vars)) {$args[] = array_shift($vars);} elseif (0 == $type && isset($vars[$name])) {$args[] = $vars[$name];} elseif (0 == $type && isset($vars[$lowerName])) {$args[] = $vars[$lowerName];} elseif ($param->isDefaultValueAvailable()) {$args[] = $param->getDefaultValue();}}
如果你实例化的类构造函数里基本很少参数或者没有,那么皆大欢喜,如果你的构造函数参数列表不少,那么请看这样的3个调用:
Container::getInstance()->get('person',[1,2]);
Container::getInstance()->get('person',[new Car(),1,2]);
Container::getInstance()->get('person',[1,'name'=>2]);
这3个调用是完全可以运行的,并且在foreach分析中走的逻辑将会走$type==1,原因是这三个的第一个元素键都是0也就是数字索引数组的第一个下标.但是如果你这样去写
Container::getInstance()->get('person',['name'=>1,2]);
会直接报错。
原因是第一个参数将会走到$type==0的操作,但是第二个参数分析不了没有返回,导致第二个参数赋值不成功报错。这是第一个缺陷的地方,因为作者的意图是想分析数字索引和字符索引的数组,当然正常情况$vars参数列表传入的都是一个普通的数字数组。所以参数列表传入一定要注意对齐它这个foreach
缺陷二:只能递归一层,来上个代码就知道了:
<?phpclass person {public function __construct(Car $obj,$a,$b){$sub = $this->add($a,$b);$obj->speed($sub);}public function add($a,$b) {return $a+$b;}
}class Car{public function __construct(App $app,$a,$b){$app->show();}public function speed($sub) {echo '我的马力很猛,高达:'.$sub;}
}class App{public function show() {echo "我是app类";}
}
现在变成了三个类,细心点你就发现,Car类我再次存放了一个依赖注入类以及一些参数。如果不对源码改造,这会直接报错,这也是一个不通用的地方,原因就是在这里:
protected function getObjectParam($className, &$vars){$array = $vars;$value = array_shift($array);if ($value instanceof $className) {$result = $value;array_shift($vars);} else {$result = $this->make($className);}return $result;}
走make逻辑的时候没有传第二个参数,也就是参数列表数组,这也就导致Car实例化的时候报错,但是如果我这么改:
<?phpclass person {public function __construct(Car $obj,$a,$b){$sub = $this->add($a,$b);$obj->speed($sub);}public function add($a,$b) {return $a+$b;}
}class Car{public function __construct(App $app){$app->show();}public function speed($sub) {echo '我的马力很猛,高达:'.$sub;}
}class App{public function show() {echo "我是app类";}
}
car类除了依赖注入的类参数,其他参数不要的话,源码是可以继续递归分析下去,感兴趣的可以在APP加一个构造函数测试。
<?phpclass person {public function __construct(Car $obj,$a,$b){$sub = $this->add($a,$b);$obj->speed($sub);}public function add($a,$b) {return $a+$b;}
}class Car{public function __construct(App $app){$app->show();}public function speed($sub) {echo '我的马力很猛,高达:'.$sub;}
}class App{public function __construct(Soft $app){$app->show();}public function show() {echo "我是app类";}
}class Soft{public function show() {echo "我是Soft类";}
}
这段代码正常执行。