index.php 第二句
// 執行HTTP應用並響應
$http = (new App())->http;
public function __construct(string $rootPath = '')
{
// 定義一些目錄地址
$this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR;
$this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
$this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
$this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
// 將provider.php中返回的類的對映合併到 $bind 變數中,相同名稱的會覆蓋,如此可以通過在該檔案中定義方式改變框架所使用的某些核心類
if (is_file($this->appPath . 'provider.php')) {
$this->bind(include $this->appPath . 'provider.php');
}
// 將當前App例項儲存到靜態屬性 $instance 中
static::setInstance($this);
// 繫結當前例項到 $instances 陣列的中,後面再有用到這個類例項時直接取這裡的
$this->instance('app', $this);
$this->instance('think\Container', $this);
}
看下App類繼承的Container類中的 bind 方法
public function bind($abstract, $concrete = null)
{
// 如果是陣列的話,就迴圈該陣列,陣列中的每個元素重走該 bind 方法,我們上面呼叫的 bind 就是先走了這裡
if (is_array($abstract)) {
foreach ($abstract as $key => $val) {
$this->bind($key, $val);
}
// 如果是匿名函式,直接儲存到 bind 中
} elseif ($concrete instanceof Closure) {
$this->bind[$abstract] = $concrete;
// 如果是一個類的例項,則儲存到 instances 陣列中
} elseif (is_object($concrete)) {
$this->instance($abstract, $concrete);
// 我們前面呼叫 bind 方法在進過第一次 foreach 迴圈後就是走這裡了,繫結到 bind 變數中
} else {
// 獲取真實類名,因為有可能傳過來的是一個別名,當然在這裡兩個都不是,依然返回了它們自己
$abstract = $this->getAlias($abstract);
$this->bind[$abstract] = $concrete;
}
return $this;
}
再看看 getAlias 方法
public function getAlias(string $abstract): string
{
// 檢查 $bind 中是否有儲存對應的類名
if (isset($this->bind[$abstract])) {
$bind = $this->bind[$abstract];
// 如果有,並且是一個字串(還有可能是閉包),那這個字串有可能還是別名,繼續查詢 $bind
if (is_string($bind)) {
return $this->getAlias($bind);
}
}
return $abstract;
}
App類中並沒有http這個成員變數,(new App())->http 會呼叫 App 類的魔術方法 __get
// 在 App 繼承的 Container類中
public function __get($name)
{
return $this->get($name);
}
public function get($abstract)
{
// 判斷 bind 變數中是否有繫結對應的類名或者 instances 變數中是否已儲存對應類的例項
// 比如這裡 $abstract=http, 在 bind 中是有的
if ($this->has($abstract)) {
// 獲取這個類的例項
return $this->make($abstract);
}
throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
}
讓我們看看 make方法裡做了什麼
/**
* 建立類的例項 已經存在則直接獲取
* @access public
* @param string $abstract 類名或者標識
* @param array $vars 變數
* @param bool $newInstance 是否每次建立新的例項
* @return mixed
*/
public function make(string $abstract, array $vars = [], bool $newInstance = false)
{
$abstract = $this->getAlias($abstract);
// 如果 instances 已儲存對應的例項,就直接返回
if (isset($this->instances[$abstract]) && !$newInstance) {
return $this->instances[$abstract];
}
// 如果是一個閉包
if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
// em....這裡還沒看
$object = $this->invokeFunction($this->bind[$abstract], $vars);
} else {
// 通過反射獲得所需要的類例項
$object = $this->invokeClass($abstract, $vars);
}
if (!$newInstance) {
// 儲存例項到 instances 陣列中
$this->instances[$abstract] = $object;
}
return $object;
}
然後是 invokeClass 方法
public function invokeClass(string $class, array $vars = [])
{
try {
// 獲取 http 的反射類
$reflect = new ReflectionClass($class);
if ($reflect->hasMethod('__make')) {
$method = new ReflectionMethod($class, '__make');
// 如果有 __make 方法,並且是公有的靜態方法,執行 __make 方法並返回,後面在例項 Request 類時會走這裡。
if ($method->isPublic() && $method->isStatic()) {
$args = $this->bindParams($method, $vars);
return $method->invokeArgs(null, $args);
}
}
// 獲取類的建構函式
$constructor = $reflect->getConstructor();
// 繫結建構函式的引數,http 這個類的構造方法需要一個App類的例項作為引數,這個方法的註釋中有些到支援依賴注入,如何實現的依賴注入就在這個 bindParams 方法中
$args = $constructor ? $this->bindParams($constructor, $vars) : [];
//
$object = $reflect->newInstanceArgs($args);
$this->invokeAfter($class, $object);
return $object;
} catch (ReflectionException $e) {
throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
}
}
bindParams 是如何實現依賴注入的
protected function bindParams($reflect, array $vars = []): array
{
// 獲取方法的引數個數,0的話直接返回空陣列
if ($reflect->getNumberOfParameters() == 0) {
return [];
}
// 判斷陣列型別 數字陣列時按順序繫結引數
reset($vars); // 將陣列的指標指到陣列的第一個元素
$type = key($vars) === 0 ? 1 : 0; // 獲取當前指標所在陣列元素的下標,判斷是不是0
$params = $reflect->getParameters(); // 獲取方法所需的引數
$args = [];
foreach ($params as $param) {
$name = $param->getName(); // app
$lowerName = Str::snake($name); // 大寫轉下劃線
$class = $param->getClass(); // 獲取引數型別限定的類 think\App
// 如果引數型別限定是一個類,這裡就是走的這個
if ($class) {
// getObjectParam 方法中再次呼叫了 make 方法來獲取需要的 App類,而這個 App 類已經在前面 new App()的時候儲存到 instances 變數中了,如此就完成了依賴注入
// 假如 App 類的建構函式中需要其它類的例項作為引數,那就會再走一遍這個流程
// 我的理解,依賴注入就是對類例項的需要通過傳參來實現,控制反轉就是這個傳參不需要使用者主動傳,交由Container完成
$args[] = $this->getObjectParam($class->getName(), $vars);
} elseif (1 == $type && !empty($vars)) {
...
}
return $args;
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結