關於依賴注入相信大家應該都經常接觸或至少有所耳聞,比較知名的框架都支持依賴注入,比如Java的Spring,PHP的Laravel、Symfony等。現在我開始手動實作一個簡陋的DI容器吧。
先開個車,為大家舉個栗子:
class Driver{ public function drive() { $car = new Car(); echo '老司机正在驾驶', $car->getCar(), PHP_EOL; } }class Car{ protected $name = '普通汽车'; public function getCar() { return $this->name; } }
有兩個類,Driver和Car,老司機Driver有個方法driver,在呼叫的時候先得整輛車$car,然後發車。大多數同學都寫過這樣或類似的代碼,這樣的代碼單看沒啥毛病,挺正常的。但是,如果我要換車,開普通車撩不到妹。
class Benz extends Car{ protected $name = '奔驰'; }
這時候就需要做一個比較噁心的操作了,得改老司機的程式碼了。 (老司機:我做錯了什麼?換車子還得讓我重學駕照…)。因此我們需要把讓Car為外界注入,將Driver和Car解耦,不是老司機自己開車的時候還要自己去造車。於是就有了下面的結果
class Driver{ protected $car; public function __construct(Car $car) { $this->car = $car; } public function drive() { echo '老司机正在驾驶', $this->car->getCar(), PHP_EOL; } }
此時Driver和Car兩個類別已經解耦,這兩個類別的依賴,依靠上層程式碼去管理。此時,老司機會這樣「開車」:
$car = new Car(); $driver = new Driver($car); $driver->drive();
此時,我們建立Driver依賴的實例,並注入。上面的例子,我們實現了依賴注入,不過是手動的,寫起來感覺還是不爽。這麼繁重的活怎麼能手動來做呢,得讓程式自己做。於是乎,DI容器誕生。
依賴注入與IoC模式類似工廠模式,是解決呼叫者和被呼叫者依賴耦合關係的模式。它解決了物件之間的依賴關係,使得物件只依賴IoC/DI容器,不再直接相互依賴,實現鬆散耦合,然後在物件創建時,由IoC/DI容器將其依賴(Dependency)的物件注入( Inject)其內,這樣做可以最大程度實現鬆散耦合。依賴注入說白一點,就是容器將某個類別依賴的其他類別的實例注入到這個類別的實例中。
這段話可能說的有點抽象,回到剛才的例子吧。剛剛我手動完成了依賴注入,比較麻煩,如果一個大型的專案這樣做肯定會覺得很繁瑣,而且不夠優雅。因此我們需要有一位總管來代替我們去幹這個,這個總管就是容器。類別的依賴管理全部交給容器去完成。因此,一般來說容器是一個全局的對象,大家共有的。
寫一個功能,我們首先需要分析問題,因此我們先要明白,對於一個簡單的DI容器需要哪些功能,這直接關係到我們程式碼的編寫。對於一個簡單的容器,至少需要滿足以下幾點:
建立所需類別的實例
完成依賴管理(DI)
可以取得單例的實例
全域唯一
class Container{ /** * 单例 * @var Container */ protected static $instance; /** * 容器所管理的实例 * @var array */ protected $instances = []; private function __construct(){} private function __clone(){} /** * 获取单例的实例 * @param string $class * @param array ...$params * @return object */ public function singleton($class, ...$params) {} /** * 获取实例(每次都会创建一个新的) * @param string $class * @param array ...$params * @return object */ public function get($class, ...$params) {} /** * 工厂方法,创建实例,并完成依赖注入 * @param string $class * @param array $params * @return object */ protected function make($class, $params = []) {} /** * @return Container */ public static function getInstance() { if (null === static::$instance) { static::$instance = new static(); } return static::$instance; } }
protected function make($class, $params = []){ //如果不是反射类根据类名创建 $class = is_string($class) ? new ReflectionClass($class) : $class; //如果传的入参不为空,则根据入参创建实例 if (!empty($params)) { return $class->newInstanceArgs($params); } //获取构造方法 $constructor = $class->getConstructor(); //获取构造方法参数 $parameterClasses = $constructor ? $constructor->getParameters() : []; if (empty($parameterClasses)) { //如果构造方法没有入参,直接创建 return $class->newInstance(); } else { //如果构造方法有入参,迭代并递归创建依赖类实例 foreach ($parameterClasses as $parameterClass) { $paramClass = $parameterClass->getClass(); $params[] = $this->make($paramClass); } //最后根据创建的参数创建实例,完成依赖的注入 return $class->newInstanceArgs($params); } }
class Container implements ArrayAccess{ /** * 单例 * @var Container */ protected static $instance; /** * 容器所管理的实例 * @var array */ protected $instances = []; private function __construct(){} private function __clone(){} /** * 获取单例的实例 * @param string $class * @param array ...$params * @return object */ public function singleton($class, ...$params) { if (isset($this->instances[$class])) { return $this->instances[$class]; } else { $this->instances[$class] = $this->make($class, $params); } return $this->instances[$class]; } /** * 获取实例(每次都会创建一个新的) * @param string $class * @param array ...$params * @return object */ public function get($class, ...$params) { return $this->make($class, $params); } /** * 工厂方法,创建实例,并完成依赖注入 * @param string $class * @param array $params * @return object */ protected function make($class, $params = []) { //如果不是反射类根据类名创建 $class = is_string($class) ? new ReflectionClass($class) : $class; //如果传的入参不为空,则根据入参创建实例 if (!empty($params)) { return $class->newInstanceArgs($params); } //获取构造方法 $constructor = $class->getConstructor(); //获取构造方法参数 $parameterClasses = $constructor ? $constructor->getParameters() : []; if (empty($parameterClasses)) { //如果构造方法没有入参,直接创建 return $class->newInstance(); } else { //如果构造方法有入参,迭代并递归创建依赖类实例 foreach ($parameterClasses as $parameterClass) { $paramClass = $parameterClass->getClass(); $params[] = $this->make($paramClass); } //最后根据创建的参数创建实例,完成依赖的注入 return $class->newInstanceArgs($params); } } /** * @return Container */ public static function getInstance() { if (null === static::$instance) { static::$instance = new static(); } return static::$instance; } public function __get($class) { if (!isset($this->instances[$class])) { $this->instances[$class] = $this->make($class); } return $this->instances[$class]; } public function offsetExists($offset) { return isset($this->instances[$offset]); } public function offsetGet($offset) { if (!isset($this->instances[$offset])) { $this->instances[$offset] = $this->make($offset); } return $this->instances[$offset]; } public function offsetSet($offset, $value) { } public function offsetUnset($offset) { unset($this->instances[$offset]); } }
$driver = $app->get(Driver::class); $driver->drive();//output:老司机正在驾驶普通汽车复制代码
$benz = $app->get(Benz::class); $driver = $app->get(Driver::class, $benz); $driver->drive();//output:老司机正在驾驶奔驰复制代码
PHP影片教學》】
以上是教你如何手動建立PHP DI容器的詳細內容。更多資訊請關注PHP中文網其他相關文章!