di php容器的建立

doublefacekill發表於2021-11-28

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

相關文章