├── v1.php ├── v2.php ├── v3.php ├── v4.php ├── v5.php ├── v6.php ├── v7.php └── README.md /v1.php: -------------------------------------------------------------------------------- 1 | auth = new Auth($dsn, $username, $password); 11 | $this->session = new Session(); 12 | } 13 | 14 | public function login($username, $password) 15 | { 16 | if ($this->auth->check($username, $password)) { 17 | $this->session->set('username', $username); 18 | return true; 19 | } 20 | return false; 21 | } 22 | } 23 | 24 | class Auth 25 | { 26 | public function __construct($dsn, $user, $pass) 27 | { 28 | echo "Connecting to '$dsn' with '$user'/'$pass'...\n"; 29 | sleep(1); 30 | } 31 | 32 | public function check($username, $password) 33 | { 34 | echo "Checking username, password from database...\n"; 35 | sleep(1); 36 | return true; 37 | } 38 | } 39 | 40 | class Session 41 | { 42 | public function set($name, $value) 43 | { 44 | echo "Set session variable '$name' to '$value'.\n"; 45 | } 46 | } 47 | 48 | $app = new App('mysql://localhost', 'root', '123456'); 49 | $username = 'jaceju'; 50 | if ($app->login($username, 'password')) { 51 | echo "$username just signed in.\n"; 52 | } 53 | -------------------------------------------------------------------------------- /v2.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 11 | $this->session = $session; 12 | } 13 | 14 | public function login($username, $password) 15 | { 16 | if ($this->auth->check($username, $password)) { 17 | $this->session->set('username', $username); 18 | return true; 19 | } 20 | return false; 21 | } 22 | } 23 | 24 | class Auth 25 | { 26 | public function __construct($dsn, $user, $pass) 27 | { 28 | echo "Connecting to '$dsn' with '$user'/'$pass'...\n"; 29 | sleep(1); 30 | } 31 | 32 | public function check($username, $password) 33 | { 34 | echo "Checking username, password from database...\n"; 35 | sleep(1); 36 | return true; 37 | } 38 | } 39 | 40 | class Session 41 | { 42 | public function set($name, $value) 43 | { 44 | echo "Set session variable '$name' to '$value'.\n"; 45 | } 46 | } 47 | 48 | $auth = new Auth('mysql://localhost', 'root', '123456'); 49 | $session = new Session(); 50 | $app = new App($auth, $session); 51 | $username = 'jaceju'; 52 | if ($app->login($username, 'password')) { 53 | echo "$username just signed in.\n"; 54 | } 55 | -------------------------------------------------------------------------------- /v3.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 11 | $this->session = $session; 12 | } 13 | 14 | public function login($username, $password) 15 | { 16 | if ($this->auth->check($username, $password)) { 17 | $this->session->set('username', $username); 18 | return true; 19 | } 20 | return false; 21 | } 22 | } 23 | 24 | interface Auth 25 | { 26 | public function check($username, $password); 27 | } 28 | 29 | class DbAuth implements Auth 30 | { 31 | public function __construct($dsn, $user, $pass) 32 | { 33 | echo "Connecting to '$dsn' with '$user'/'$pass'...\n"; 34 | sleep(1); 35 | } 36 | 37 | public function check($username, $password) 38 | { 39 | echo "Checking username, password from database...\n"; 40 | sleep(1); 41 | return true; 42 | } 43 | } 44 | 45 | class Session 46 | { 47 | public function set($name, $value) 48 | { 49 | echo "Set session variable '$name' to '$value'.\n"; 50 | } 51 | } 52 | 53 | $auth = new DbAuth('mysql://localhost', 'root', '123456'); 54 | $session = new Session(); 55 | $app = new App($auth, $session); 56 | $username = 'jaceju'; 57 | if ($app->login($username, 'password')) { 58 | echo "$username just signed in.\n"; 59 | } 60 | -------------------------------------------------------------------------------- /v4.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 11 | $this->session = $session; 12 | } 13 | 14 | public function login($username, $password) 15 | { 16 | if ($this->auth->check($username, $password)) { 17 | $this->session->set('username', $username); 18 | return true; 19 | } 20 | return false; 21 | } 22 | } 23 | 24 | interface Auth 25 | { 26 | public function check($username, $password); 27 | } 28 | 29 | class DbAuth implements Auth 30 | { 31 | public function __construct($dsn, $user, $pass) 32 | { 33 | echo "Connecting to '$dsn' with '$user'/'$pass'...\n"; 34 | sleep(1); 35 | } 36 | 37 | public function check($username, $password) 38 | { 39 | echo "Checking username, password from database...\n"; 40 | sleep(1); 41 | return true; 42 | } 43 | } 44 | 45 | class HttpAuth implements Auth 46 | { 47 | public function check($username, $password) 48 | { 49 | echo "Checking username, password from HTTP Authentication...\n"; 50 | sleep(1); 51 | return true; 52 | } 53 | } 54 | 55 | class Session 56 | { 57 | public function set($name, $value) 58 | { 59 | echo "Set session variable '$name' to '$value'.\n"; 60 | } 61 | } 62 | 63 | // $auth = new DbAuth('mysql://localhost', 'root', '123456'); 64 | $auth = new HttpAuth(); 65 | $session = new Session(); 66 | $app = new App($auth, $session); 67 | $username = 'jaceju'; 68 | if ($app->login($username, 'password')) { 69 | echo "$username just signed in.\n"; 70 | } 71 | -------------------------------------------------------------------------------- /v5.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 11 | $this->session = $session; 12 | } 13 | 14 | public function login($username, $password) 15 | { 16 | if ($this->auth->check($username, $password)) { 17 | $this->session->set('username', $username); 18 | return true; 19 | } 20 | return false; 21 | } 22 | } 23 | 24 | interface Auth 25 | { 26 | public function check($username, $password); 27 | } 28 | 29 | class DbAuth implements Auth 30 | { 31 | public function __construct($dsn, $user, $pass) 32 | { 33 | echo "Connecting to '$dsn' with '$user'/'$pass'...\n"; 34 | sleep(1); 35 | } 36 | 37 | public function check($username, $password) 38 | { 39 | echo "Checking username, password from database...\n"; 40 | sleep(1); 41 | return true; 42 | } 43 | } 44 | 45 | class HttpAuth implements Auth 46 | { 47 | public function check($username, $password) 48 | { 49 | echo "Checking username, password from HTTP Authentication...\n"; 50 | sleep(1); 51 | return true; 52 | } 53 | } 54 | 55 | class Container 56 | { 57 | protected static $map = []; 58 | 59 | public static function register($name, $class, $args = null) 60 | { 61 | static::$map[$name] = [$class, $args]; 62 | } 63 | 64 | public static function get($name) 65 | { 66 | list($class, $args) = isset(static::$map[$name]) ? 67 | static::$map[$name] : 68 | [$name, null]; 69 | 70 | if (class_exists($class, true)) { 71 | $reflectionClass = new ReflectionClass($class); 72 | return !empty($args) ? 73 | $reflectionClass->newInstanceArgs($args) : 74 | new $class(); 75 | } 76 | 77 | return null; 78 | } 79 | } 80 | 81 | class Session 82 | { 83 | public function set($name, $value) 84 | { 85 | echo "Set session variable '$name' to '$value'.\n"; 86 | } 87 | } 88 | 89 | Container::register('Auth', 'DbAuth', ['mysql://localhost', 'root', '123456']); 90 | Container::register('Auth', 'HttpAuth'); 91 | 92 | $auth = Container::get('Auth'); 93 | $session = Container::get('Session'); 94 | $app = new App($auth, $session); 95 | $username = 'jaceju'; 96 | if ($app->login($username, 'password')) { 97 | echo "$username just signed in.\n"; 98 | } -------------------------------------------------------------------------------- /v6.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 11 | $this->session = $session; 12 | } 13 | 14 | public function login($username, $password) 15 | { 16 | if ($this->auth->check($username, $password)) { 17 | $this->session->set('username', $username); 18 | return true; 19 | } 20 | return false; 21 | } 22 | } 23 | 24 | interface Auth 25 | { 26 | public function check($username, $password); 27 | } 28 | 29 | class DbAuth implements Auth 30 | { 31 | public function __construct($dsn, $user, $pass) 32 | { 33 | echo "Connecting to '$dsn' with '$user'/'$pass'...\n"; 34 | sleep(1); 35 | } 36 | 37 | public function check($username, $password) 38 | { 39 | echo "Checking username, password from database...\n"; 40 | sleep(1); 41 | return true; 42 | } 43 | } 44 | 45 | class HttpAuth implements Auth 46 | { 47 | public function check($username, $password) 48 | { 49 | echo "Checking username, password from HTTP Authentication...\n"; 50 | sleep(1); 51 | return true; 52 | } 53 | } 54 | 55 | class Container 56 | { 57 | protected static $map = []; 58 | 59 | public static function register($name, $class, $args = null) 60 | { 61 | static::$map[$name] = [$class, $args]; 62 | } 63 | 64 | public static function get($name) 65 | { 66 | list($class, $args) = isset(static::$map[$name]) ? 67 | static::$map[$name] : 68 | [$name, null]; 69 | 70 | if (class_exists($class, true)) { 71 | $reflectionClass = new ReflectionClass($class); 72 | return !empty($args) ? 73 | $reflectionClass->newInstanceArgs($args) : 74 | new $class(); 75 | } 76 | 77 | return null; 78 | } 79 | 80 | public static function inject() 81 | { 82 | $lists = func_get_args(); 83 | $callback = array_pop($lists); 84 | $injectArgs = []; 85 | 86 | foreach ($lists as $name) { 87 | $injectArgs[] = Container::get($name); 88 | } 89 | 90 | return call_user_func_array($callback, $injectArgs); 91 | } 92 | } 93 | 94 | class Session 95 | { 96 | public function set($name, $value) 97 | { 98 | echo "Set session variable '$name' to '$value'.\n"; 99 | } 100 | } 101 | 102 | if ('production' === getenv('APP_ENV')) { 103 | Container::register('Auth', 'DbAuth', ['mysql://localhost', 'root', '123456']); 104 | } else { 105 | Container::register('Auth', 'HttpAuth'); 106 | } 107 | 108 | $app = Container::inject('Auth', 'Session', function ($auth, $session) { 109 | return new App($auth, $session); 110 | }); 111 | 112 | $username = 'jaceju'; 113 | if ($app->login($username, 'password')) { 114 | echo "$username just signed in.\n"; 115 | } 116 | -------------------------------------------------------------------------------- /v7.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 11 | $this->session = $session; 12 | } 13 | 14 | public function login($username, $password) 15 | { 16 | if ($this->auth->check($username, $password)) { 17 | $this->session->set('username', $username); 18 | return true; 19 | } 20 | return false; 21 | } 22 | } 23 | 24 | interface Auth 25 | { 26 | public function check($username, $password); 27 | } 28 | 29 | class DbAuth implements Auth 30 | { 31 | public function __construct($dsn, $user, $pass) 32 | { 33 | echo "Connecting to '$dsn' with '$user'/'$pass'...\n"; 34 | sleep(1); 35 | } 36 | 37 | public function check($username, $password) 38 | { 39 | echo "Checking username, password from database...\n"; 40 | sleep(1); 41 | return true; 42 | } 43 | } 44 | 45 | class HttpAuth implements Auth 46 | { 47 | public function check($username, $password) 48 | { 49 | echo "Checking username, password from HTTP Authentication...\n"; 50 | sleep(1); 51 | return true; 52 | } 53 | } 54 | 55 | class Container 56 | { 57 | protected static $map = []; 58 | 59 | public static function register($name, $class, $args = null) 60 | { 61 | static::$map[$name] = [$class, $args]; 62 | } 63 | 64 | public static function get($name) 65 | { 66 | list($class, $args) = isset(static::$map[$name]) ? 67 | static::$map[$name] : 68 | [$name, null]; 69 | 70 | if (class_exists($class, true)) { 71 | $reflectionClass = new ReflectionClass($class); 72 | return !empty($args) ? 73 | $reflectionClass->newInstanceArgs($args) : 74 | new $class(); 75 | } 76 | 77 | return null; 78 | } 79 | 80 | public static function inject() 81 | { 82 | $lists = func_get_args(); 83 | $callback = array_pop($lists); 84 | $injectArgs = []; 85 | 86 | foreach ($lists as $name) { 87 | $injectArgs[] = Container::get($name); 88 | } 89 | 90 | return call_user_func_array($callback, $injectArgs); 91 | } 92 | 93 | public static function resolve($name) 94 | { 95 | if (!class_exists($name, true)) { 96 | return null; 97 | } 98 | 99 | $reflectionClass = new ReflectionClass($name); 100 | $reflectionConstructor = $reflectionClass->getConstructor(); 101 | $reflectionParams = $reflectionConstructor->getParameters(); 102 | 103 | $args = []; 104 | foreach ($reflectionParams as $param) { 105 | $class = $param->getClass()->getName(); 106 | $args[] = static::get($class); 107 | } 108 | 109 | return !empty($args) ? 110 | $reflectionClass->newInstanceArgs($args) : 111 | new $class(); 112 | } 113 | } 114 | 115 | class Session 116 | { 117 | public function set($name, $value) 118 | { 119 | echo "Set session variable '$name' to '$value'.\n"; 120 | } 121 | } 122 | 123 | if ('production' !== getenv('APP_ENV')) { 124 | Container::register('Auth', 'DbAuth', ['mysql://localhost', 'root', '123456']); 125 | } else { 126 | Container::register('Auth', 'HttpAuth'); 127 | } 128 | 129 | $app = Container::resolve('App'); 130 | $username = 'jaceju'; 131 | if ($app->login($username, 'password')) { 132 | echo "$username just signed in.\n"; 133 | } 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 理解 Dependency Injection 實作原理 2 | 3 | 現代較新的 Web Framework 都強調自己有 Dependency Injection (以下簡稱 DI ) 的特色,只是很多人對它的運作原理還是一知半解。 4 | 5 | 所以接下來我將用一個簡單的範例,來為各位介紹在 PHP 中如何實現簡易的 DI 。 6 | 7 | 8 | 9 | ## 基本範例 10 | 11 | 這是一個應用程式的範例,它只包含了登入處理程序。在這個範例中, `App` 類別的建構式參考了新的 `Auth` 與 `Session` 的物件實體,並在 `App::login()` 中使用。 12 | 13 | 註:請特別注意,為了呈現重點,我忽略掉很多程式碼,同時也沒有進行良好的架構設計;所以請不要把這個範例用在你的程式中,或是對為什麼我沒有進行錯誤處理,以及為什麼要採用奇怪的設計提出質疑。 14 | 15 | ```php 16 | auth = new Auth($dsn, $username, $password); 25 | $this->session = new Session(); 26 | } 27 | 28 | public function login($username, $password) 29 | { 30 | if ($this->auth->check($username, $password)) { 31 | $this->session->set('username', $username); 32 | return true; 33 | } 34 | return false; 35 | } 36 | } 37 | ?> 38 | ``` 39 | 40 | 而 `Auth` 類別是從資料庫驗證使用者身份,這裡我僅用簡單的描述來呈現效果。 41 | 42 | ```php 43 | 58 | ``` 59 | 60 | `Session` 類別也是概念性的實作: 61 | 62 | ```php 63 | 72 | ``` 73 | 74 | 最後我們讓程式動起來, client 程式如下: 75 | 76 | ```php 77 | login($username, 'password')) { 81 | echo "$username just signed in.\n"; 82 | } 83 | ?> 84 | ``` 85 | 86 | 註:這裡的 client 程式指的是實際操作這些物件實體的程式。 87 | 88 | 各位可以先試著想想這個程式在可擴充性上有什麼問題?例如我想把身份認證方式換成第三方服務的機制,或是改用其他媒介來存放 session 內容等。 89 | 90 | 還有如果想在沒有資料庫連線、或是沒有 HTTP session 的環境下對 `App::login()` 方法的邏輯進行隔離測試,各位會怎麼做呢? 91 | 92 | ## 解除依賴關係 93 | 94 | 上面的範例因為 `App` 類別已經依賴了 `Auth` 類別和 `Session` 類別,而這兩個類別都有實作跟系統環境有關的程式邏輯,這麼一來就會讓 `App` 類別難以進行底層機制的切換或是隔離測試。所以接下來我們要做的,就是把它們的依賴關係解除。 95 | 96 | 修改後的 `App` 類別如下: 97 | 98 | ```php 99 | auth = $auth; 108 | $this->session = $session; 109 | } 110 | } 111 | 112 | $auth = new Auth('mysql://localhost', 'root', '123456'); 113 | $session = new Session(); 114 | $app = new App($auth, $session); 115 | ?> 116 | ``` 117 | 118 | 首先我們在 `App` 類別的建構式 `__construct` 原本的資料庫設定參數移除,並將原來直接以 `new` 關鍵字所產生的物件實體,改用方法參數的方式來注入。而使用 `new` 關鍵字產生物件實體的程式碼,就移到 `App` 類別外。 119 | 120 | 這種「將依賴的類別改用方法參數來注入」的作法,就是我們說的「依賴注入 (Dependency Injection) 」。 121 | 122 | 常見依賴注入的方式有兩種: Constructor Injection 及 Setter Injection 。它們的實作形式並沒有什麼不同,差別只在於是不是類別建構式而已。 123 | 124 | 不過 Constructor Injection 必須在建立物件實體時就進行注入,而 Setter Injection 則是可以在物件實體建立後才透過 setter 函式來進行注入。而這裡為了方便解說,我採用的是 Constructor Injection 。 125 | 126 | ## 依賴抽象介面 127 | 128 | 好了,現在的問題是 `Auth` 類別的實作還是依賴在資料庫上,所以我們也要讓 `Auth` 類別跟資料庫之間解除依賴關係,讓它成為一個抽象介面。 129 | 130 | 這裡的抽象介面是指觀念上的意義,而非語言層級上的抽象類別 (Abstract Class) 或介面 (Interface) 。至於在實作上該用抽象類別還是介面,在這個範例裡並沒有差別,大家可以自行判斷;這裡我用介面 (Interface) ,因為我僅需要 `Auth::check()` 這個介面方法的定義而已。 131 | 132 | 這一步首先我把原來的 `Auth` 類別重新命名為 `DbAuth` 類別: 133 | 134 | ```php 135 | 150 | ``` 151 | 152 | 接著建立一個 `Auth` 介面,它包含了 `Auth::check()` 方法的定義: 153 | 154 | ```php 155 | 161 | ``` 162 | 163 | 然後讓 `DbAuth` 類別實作 `Auth` 介面: 164 | 165 | ```php 166 | 172 | ``` 173 | 174 | 最後把原來初始化 `Auth` 類別的物件實體的程式碼,改為初始化 `DbAuth` 的物件實體。 175 | 176 | ```php 177 | 182 | ``` 183 | 184 | 透過 `Auth` 介面的幫助,我們已經讓 `App` 類別與實際的資料庫操作類別分離開來了。現在只要是實作 `Auth` 介面的類別,都可以被 `App` 類別所接受,例如我們可能會改用 HTTP 認證來取代資料庫認證: 185 | 186 | ```php 187 | 201 | ``` 202 | 203 | 當然其他類型的認證方式也可以透過建立新的類別來使用,而不會影響到 `App` 類別的內部實作。 204 | 205 | ## DI 容器 206 | 207 | 現在又有個問題, client 程式還是依賴於 `DbAuth` 類別或是 `HttpAuth` 類別;通常這種狀況在需要編譯型的語言 (例如 Java ) 中,程式一旦編譯完成佈署出去後,就很難再進行修改。 208 | 209 | 如果我們可以改用設定的方式來告訴程式,在不同的狀況下對應不同的類別,然後讓程式自行判斷環境來產生需要的物件實體,這樣就可以解開 client 程式對實作類別的依賴關係。 210 | 211 | 這裡要引入一個技術,稱為 DI 容器 (Dependency Injection Container) 。 DI 容器主要的作用在於幫我們解決產生物件實體時,應該參考哪一個類別。我們先來看看用法: 212 | 213 | ```php 214 | 221 | ``` 222 | 223 | 首先我們在 DI 容器中先以 `Container::register()` 方法來註冊 `Auth` 這個別名實際上要對應哪個類別,以及建立物件實體時會用到的初始化參數。要注意,這裡的別名並不是指真正的類別或介面,但我們可以用相同的名稱以避免認知上的問題。 224 | 225 | 然後我們用 `Container::get()` 方法取得別名所對應類別的物件實體,上面例子裡的 `$auth` 就是 `DbAuth` 類別的物件實體。 226 | 227 | 這麼一來,我們就可以把註冊的程式碼移出 client 程式之外,並將註冊參數改用設定檔引入,順利解開 client 程式對實作類別的依賴。 228 | 229 | ## DI 容器原理 230 | 231 | 那麼 DI 容器的原理是怎麼運作的呢?首先在 `Container::register()` 方法註冊的部份,它其實只是把參數記到 `$map` 這個類別靜態屬性裡。 232 | 233 | ```php 234 | 247 | ``` 248 | 249 | 重點在 `Container::get()` 方法,它透過 `$name` 別名,把 `$map` 屬性中對應的類別名稱和初始化參數取出;接著判斷類別是不是存在,如果存在的話就建立對應的物件實體。 250 | 251 | ```php 252 | newInstanceArgs($args) : 267 | new $class(); 268 | } 269 | 270 | return null; 271 | } 272 | } 273 | ?> 274 | ``` 275 | 276 | 比較特別的是,如果初始化參數不是空值 (`null`) 時,則必須透過 `ReflectionClass::newInstanceArgs()` 方法來建立物件實體。 `ReflectionClass` 類別可以映射出指定類別的內部結構,並提供方法來操作這個結構; Reflection 是現代語言常見的機制, PHP 在這方面也提供了完整的 API 供開發者使用,請參考: [PHP: Reflection](http://php.net/manual/en/book.reflection.php) 。 277 | 278 | `Container::get()` 方法也可以在沒有註冊的狀況下,直接把別名當成類別名稱,然後協助我們初始化對應的物件實體;例如: 279 | 280 | ```php 281 | 284 | ``` 285 | 286 | ## 手動注入 287 | 288 | 現在我們的 client 程式已經修改成以下的樣子: 289 | 290 | ```php 291 | 296 | ``` 297 | 298 | 不過當初始化參數較多的狀況下,重複寫好幾次 `Container::get()` 看起來也是挺囉嗦的。 299 | 300 | 接下來我們實作一個 `Container::inject()` 方法,提供開發者可以一次注入所有依賴物件實體: 301 | 302 | ```php 303 | 308 | ``` 309 | 310 | 這裡我們讓 `Container::inject()` 接受不定個數的參數,除了最後一個參數必須是 callback 型態外,其他都是要傳遞給 `Container::get()` 的參數。 `Container::inject()` 的實作方式如下: 311 | 312 | ```php 313 | 331 | ``` 332 | 333 | 在參數個數不定的狀況下,可以用 `func_get_args()` 函式來取得所有參數;而 `array_pop()` 可以取出最後一個參數值做為 callback 。剩下的參數就透過 `Container::get()` 來取得物件實體,最後再透過 `call_user_func_array()` 函式將處理好的參數傳遞給 callback 執行。 334 | 335 | ## 自動解決所有依賴注入 336 | 337 | 在我們的範例裡, `Container` 類別如果可以提供一個方法,自動為我們解決所有 `App` 類別依賴問題,那麼程式就可以更乾淨些。 338 | 339 | 要做到這點,我們就必須知道要注入的方法所需要參數的類型;而在 PHP 中的 [Type Hinting](http://php.net/manual/en/language.oop5.typehinting.php) ,就可以告訴我們參數所對應的變數類型或類別。 340 | 341 | 回到 `App::__construct()` 建構子上,我們看到 `$auth` 與 `$session` 兩個參數的 type hint 分別對應到 `Auth` 與 `Session` 這兩個類別,剛好就可以用來當做我們做自動依賴注入的條件。 342 | 343 | ```php 344 | 352 | ``` 353 | 354 | 接著我們為 `Container` 類別提供一個 `resolve()` 方法,它可以接受一個類別名稱用來建立物件實體,而不需要再使用 `new` 關鍵字。 355 | 356 | ```php 357 | 360 | ``` 361 | 362 | 我們希望 `Container::resolve()` 方法會自動產生參數所對應的物件,解決這個類別建構子所需要的依賴關係。它的實作如下: 363 | 364 | ```php 365 | getConstructor(); 378 | $reflectionParams = $reflectionConstructor->getParameters(); 379 | 380 | $args = []; 381 | foreach ($reflectionParams as $param) { 382 | $class = $param->getClass()->getName(); 383 | $args[] = static::get($class); 384 | } 385 | 386 | return !empty($args) ? 387 | $reflectionClass->newInstanceArgs($args) : 388 | new $class(); 389 | } 390 | } 391 | ?> 392 | ``` 393 | 394 | `Container::resolve()` 方法與 `Container::get()` 方法的原理類似,但較特別的是它使用了 `ReflectionClass::getConstructor()` 方法來取得類別建構子的 `ReflectionMethod` 實體;接著再用 `ReflectionMethod::getParameters()` 取出參數的 `ReflectionParameter` 物件集合 (陣列) 。 395 | 396 | 而後我們就可以在迴圈中一一透過 `ReflectionParameter::getClass()` 方法與 `ReflectionClass::getName()` 方法來取得 type hint 所指向的類別或介面名稱。當有了參數所對應的類別或介面名稱後,就可以用 `Container::get()` 方法來取得參數的物件實體。 397 | 398 | 最後把這些物件帶回建構子的參數裡,並初始化我們所需要的物件實體,就完成了 `App` 類別的自動依賴注入。 399 | 400 | ## 深入思考 401 | 402 | 再強調一次,這裡的範例只是為了介紹 DI 容器的原理,並不能真正用在實務上。因為一個完整的 DI 容器還要考慮以下的問題: 403 | 404 | * 類別不存在時的處理。 405 | * 與其他非類別的參數整合。 406 | * 如何建立設定檔機制以便切換依賴關係。 407 | * 遞迴地自動注入物件實體。 408 | * 取得 Singleton 物件實體。 409 | * 可以透過原始碼上的 DocBlock 註解來註明依賴關係。 410 | 411 | 目前已經有很多 DI Framework 幫我們處理好這些事情了,建議大家如果真的需要在專案中使用 DI 時,應該採用這些 Framework 。 412 | 413 | ## 總結 414 | 415 | 如果專案並不會有太多變化性,那麼依賴注入對我們來說就不是那麼重要。但是如果希望程式對特定類別的依賴性降低,只針對抽象介面實作,那麼依賴注入就有其必要性。 416 | 417 | 在 PHP 上的 DI 容器的基本實作原理也不複雜,透過 Reflection 機制就可以看到類別內部的結構,讓我們對它的建構子注入我們想要的參數值。 418 | 419 | DI 容器要考量的部份也不少,但這些功能都已經有 Framework 實作,我們應該在專案中使用它們而儘可能不要自行開發。 420 | 421 | 希望透過以上的介紹,可以讓大家對 Framework 的依賴注入機制有基本的認知。 --------------------------------------------------------------------------------