di php容器的创建
先来确定一下容器最基础的功能,首先需要容器能存储对象,提取对象。
容器中的对象可以放在一个数组中统一管理,可以把类名作为key,对象作为value,把对象存在数组中。
提取对象的时候只需要传入类名(key)就可以得到对象。
有些时候直接用类名输入得到对象的方式不方便,比如有些类的名称加上命名空间后很长,而且直接输入类名语义不够明显。
所以可以考虑用一个数组记录类名对应的别名。
所以一共要两个数组 $bind, $instance//$bind记录类的别名和类名的映射 类似于这种:
$bind = [‘Container’ => ‘\think\Container’, ‘Event’ =>’\think\Event’];
$instance记录类名和对象的映射 类似于这种:
$instances = [‘\think\Container’ => new \think\Container, ‘\think\Event’ => function(){return new \think\Event}];
容器类的主要功能就是对这两个数组的增删改查
现在来开始写代码,首先需要创建一个容器类。可以使用单例模式来保存容器对象
class Container
{
protected static $instance;
protected $instances = [];
protected $bind = [];
public static function getInstance()
{
if (is_null(static::$instance)) {
static::$instance = new static;
}
if (static::$instance instanceof Closure) {
return (static::$instance)();
}
return static::$instance;
}
public static function setInstance($instance)
{
static::$instance = $instance;
}
}
容器要有最基础的可供外部使用的几个方法 :
bind($abstract, $concrete);绑定类的标识。
$abstract理解为类的别名,$concrete理解为类名
instance(string $abstract, $instance)绑定类名->对象
get($abstract)通过类名获取对象
判断容器中是否存在类及标识
public function bound(string $abstract):bool
{
return isset($this->bind[$abstract]) || isset($this>instances[$abstract]);
}
根据别名获取真是类名
public function getAlias(string $abstract):string
{
if(isset($this->bind[$abstract])) {
$bind = $this->bind[$abstract];
if (is_string($bind)) {
return $this->getAlias($bind);
}
}
return $abstract;
}
判断容器中是否存在对象的实例
public function exists(string $abstract)
{
$abstract = $this->getAlias($abstract);
return isset($this->instances[$abstract]);
}
绑定一个类、闭包、实例、接口实现到容器
如果$abstract输入的是数组那么把数组的key,value递归的绑定
如果$concrete传入闭包直接放入$bind数组中
如果$concrete是对象则放入$instances数组中
如果是字符串则放入$bind中
public function bind($abstract, $concrete = null)
{
if (is_array($abstract)) {
foreach ($abstract as $key => $val) {
$this->bind($key, $val);
}
}
elseif ($concrete instanceof Closure) {
$this->bind[$abstract] = $concrete;
} elseif (is_object($concrete)) {
$this->instances[$abstract] = $concrete;
}
else {
$abstract = $this->getAlias($abstract);
if ($abstract != $concrete) {
$this->bind[$abstract] = $concrete;
}
}
}
输入类名(或类的别名,映射到$instances数组中),$concreate为类的实例
public function instance(string $abstract, $concrete)
{
$abstract = $this->getAlias($abstract);
$this->instances[$abstract] = $concrete;
return $this;
}
下面几个是需要通过php反射的api解析依赖的过程。比如创建A类对象的时候
A类对象在构造函数中需要依赖B类,这个时候就需要容器来解决依赖的问题
先用ReflectionClass系统提供的类活的类的信息,在用getConstructor方法获得构造函数信息。获得构造函数信息后用getNumberOfParameters方法获得构造函数依赖的参数个数,如果依赖参数的个数为0,则可以直接创建。
getParameters获得具体的参数信息。根据参数的类型去容器中寻找对应类型的参数(getType),如果没有的话,则检查函数是否有默认值(getDefaultValue)。如果都没有的话,则报错,对象无法创建
public function getObjectParam(string $className, array &$vars)
{
$array = $vars;
$value = array_shift($array);
if ($value instanceof $className) {
array_shift($vars);
return $value;
} else {
return $this->make($className);
}
}
protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array
{
if ($reflect->getNumberOfParameters() == 0) {
return [];
}
reset($vars);
$type = key($vars) === 0 ? 1 : 0;
$params = $reflect->getParameters();
$args = [];
foreach ($params as $param) {
$name = $param->getName();
$reflectionType = $param->getType();
if ($reflectionType && $reflectionType->isBuiltin() === false) {
$args[] = $this->getObjectParam($reflectionType->getName(), $vars);
} elseif ($type == 1 && !empty($vars)) {
$args[] = array_shift($vars);
} elseif ($type == 0 && array_key_exists($name, $vars)) {
$args[] = $vars[$name];
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
throw new InvalidArgumentException('method param miss:' . $name);
}
}
return $args;
}
public function invokeFunction($function, array $vars = [])
{
try {
$reflect = new ReflectionFunction($function);
} catch (\ReflectionException $e) {
throw new ReflectionException("function not exists: {$function}()", $function, $e);
}
$args = $this->bindParams($reflect, $vars);
return $function(...$args);
}
public function invokeClass(string $class, array $vars = [])
{
try {
$reflect = new ReflectionClass($class);
} catch (ReflectionException $e) {
throw new ReflectionException('class not exists: ' . $class, $class, $e);
}
$constructor = $reflect->getConstructor();
$args = $constructor ? $this->bindParams($constructor, $vars): [];
$object = $reflect->newInstanceArgs($args);
return $object;
}
public function make(string $abstract, array $vars = [], bool $newInstance = false)
{
$abstract = $this->getAlias($abstract);
if (isset($this->instances[$abstract]) && !$newInstance) {
return $this->instances[$abstract];
}
if ($this->bind[$abstract] && $this->bind[$abstract] instanceof Closure) {
$object = $this->invokeFunction($this->bind[$abstract], $vars);
} else {
$object = $this->invokeClass($abstract, $vars);
}
if (!$newInstance) {
$this->instances[$abstract] = $object;
}
return $object;
}
public function get($abstract)
{
if ($this->bound($abstract)) {
return $this->make($abstract);
}
throw new \Exception('class not exists: ' . $abstract, $abstract);
}
}
本作品采用《CC 协议》,转载必须注明作者和本文链接