先來確定一下容器最基礎的功能,首先需要容器能儲存物件,提取物件。
容器中的物件可以放在一個陣列中統一管理,可以把類名作為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 協議》,轉載必須註明作者和本文連結