lumen-ioc容器测试 (6)
问题四、我们现在遇到的问题是构建对象的时候需要明确构造函数的参数类型、数量。并且增加程序的透明度
# class Container# 复制上面的代码 class Person{ private $name; public function __construct($param) { $this->name = $param['name'] ?? 'unknown'; } public function getName() { return $this->name; } }
我们上一个版本的解决方案显然不是很理想。我们不能要求每个类的构造函数都传一个$param
数组,这样不透明也不现实
那么如果我们要这样使用一个容器呢?
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是一个程序员' :''; 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) { # 在这里我们找到了判断初始化函数的契机,利用反射我们可以做很多事,包括我们的问题 # 1.获得一个$concrete类反射 $reflector = new ReflectionClass($concrete); # 2.判断这个类能否被实例化,例如 private function __construct(){} if(!$reflector->isInstantiable()) { throw new Exception("{$concrete} 无法被实例化"); } # 3.获取构造函数反射方法 $constructor = $reflector->getConstructor(); # 4.获取参数列表 $parameters = $constructor->getParameters(); # 5.遍历参数列表 $instances = []; foreach ($parameters as $_parameter) { # 如果已经$parameter中已经设置了对应的参数 if(isset($parameter[$_parameter->name])) { $instances[] = $parameter[$_parameter->name]; continue; } # 如果没设置判断一下这个参数是否存在默认值 if(!$_parameter->isDefaultValueAvailable()) { throw new Exception("{$concrete} 无法被实例化,缺少参数{$_parameter->name}"); } $instances[] = $_parameter->getDefaultValue(); } # 这里就需要通过反射来构建对象了 // 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']里存储的都是通过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是一个程序员' :''; return "姓名: {$this->name} $message"; } } $container = new Container(); $container->bind('Person'); $p1 = $container->make('Person',[ 'name' => 'lilei', ]); echo $p1->me();