容器实现类的实例自动绑定依靠了反射,但是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
缺陷二:只能递归一层,来上个代码就知道了:
<?php
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 $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实例化的时候报错,但是如果我这么改:
<?php
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 $app)
{
$app->show();
}
public function speed($sub) {
echo '我的马力很猛,高达:'.$sub;
}
}
class App{
public function show() {
echo "我是app类";
}
}
car类除了依赖注入的类参数,其他参数不要的话,源码是可以继续递归分析下去,感兴趣的可以在APP加一个构造函数测试。
<?php
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 $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类";
}
}
这段代码正常执行。