laravel的IOC Container能自動解析依賴,很逆天很神奇,那麼它背後的實現原理是怎麼樣的呢?裡面有什麼rocket science呢?
其實也沒啥,背後用的是PHP5開始自帶的對映(reflection)功能,或者說反射功能,又經常稱作是reflection api,它能反向地解析提交給它的class、method、extension等,基於這些資訊,你可以分析出一個class的型別,需要哪些依賴,有哪些屬性,父類子類情況等等,然後去相應地構建例項,就可以實現laravel的自動依賴解析功能了。
這裡呢,我們先不看laravel自動依賴解析的具體程式碼,我們先來看看這個PHP的reflection api是什麼鬼,尤其是其中的ReflectionClass,也即是專門用來反向解析class的。
class Foo
{
public $name = 'pilishen';
public $project = 'laravel';
protected $bar;
//Constructor
public function __construct(Bar $bar)
{
$this->bar = $bar;
}
public function name()
{
echo $this->name."\n";
}
public function project()
{
echo $this->project."\n";
}
}
獲取類名、名稱空間、檔名:
$reflection = new ReflectionClass('Foo');
echo $reflection->getName();
就能輸出Foo
也即這個class
的name
,相關的還有一個很明顯的getShortName()
.
如果你想獲取該class
所在的檔案路徑及名稱,那麼可以使用getFileName()
方法,比如我的顯示:
string '/home/vagrant/Code/php-test/index.php' (length=37)
當然,獲取名稱空間(namespace
)就是getNamespaceName()
獲取各類屬性或引數:
var_dump($reflection->getDefaultProperties());
就能獲取其預設屬性及值:
array (size=3)
'name' => string 'pilishen' (length=8)
'project' => string 'laravel' (length=7)
'bar' => null
可能你會想到get_class_vars
或者get_object_vars
,假設這個時候我們只想獲取其protected
屬性怎麼辦呢?
$props = $reflection->getProperties(ReflectionProperty::IS_PROTECTED);
var_dump($props);
這個時候顯示:
array (size=1)
0 =>
object(ReflectionProperty)[2]
public 'name' => string 'bar' (length=3)
public 'class' => string 'Foo' (length=3)
也即可以通過在getProperties()
中傳遞filter
引數來篩選要獲取的屬性,當然實際當中,你可以通過下面的方式來分別獲取每個屬性的name
:
foreach ($props as $prop) {
print $prop->getName() . "\n";
}
屬性相關的其他方法:
getProperty()
:獲取某一個特定屬性,比如 $class->getProperty('name')
;
getStaticProperties()
:獲取所有的靜態屬性
getStaticPropertyValue()
:獲取特定的靜態屬性的value
setStaticPropertyValue()
:將某個已有的靜態屬性值設為新的值,注意必須是已有的,你不能通過它來新增新的靜態屬性
hasProperty()
:檢視某個特定的屬性是否存在
hasConstant()
:檢視某個特定的常量(const)是否存在
獲取constructor資訊:
說白了一旦獲取到了constructor
,往往也就能知道這個class
的依賴有哪些了,執行:
var_dump($reflection->getConstructor());
就可以看到:
object(ReflectionMethod)[2]
public 'name' => string '__construct' (length=11)
public 'class' => string 'Foo' (length=3)
如果不存在constructor
就會返回null
,所以實際當中可以通過is_null()
來做進一步判斷。接下來執行:
$constructor = $reflection->getConstructor();
var_dump($constructor->getParameters());
就會以array
的形式返回constructor
裡的具體資訊,每一條都是一個object
:
array (size=1)
0 =>
object(ReflectionParameter)[3]
public 'name' => string 'bar' (length=3)
然後我們就可以通過遍歷的形式獲取每一個具體的parameter
,在每個parameter
上去獲取它相應的型別宣告(type declaration)
$constructor = $reflection->getConstructor();
$parameters = $constructor->getParameters();
foreach ($parameters as $parameter) {
var_dump($parameter->getClass());
}
就可以看到:
object(ReflectionClass)[4]
public 'name' => string 'Bar' (length=3)
如果不存在class
,那麼返回的是null
,說明傳的只是一個普通引數,沒有進行類宣告,就可以進行其他相應操作.
比如可以呼叫isDefaultValueAvailable()
來判斷這個引數有沒有預設值,然後通過getDefaultValue()
來獲取其預設值。而返回的$class = $parameter->getClass()
,可以進一步通過$class->name
獲取其class
名稱,然後就可以相應地去構建依賴例項了。
跟自動構建例項相關的其他方法:isInstantiable()
: 判斷一個Class
或者傳參能否被例項化,比如interface
和abstract class
就不能被例項化,這個一般用在進行反向解析最開始的地方,比如如果不能例項化,也就沒必要去獲取其constructor
相關資訊了;
newInstanceArgs()
: 基於你傳遞的引數來建立一個新的例項,這裡傳進去的引數,也就是constructor
裡需要傳進去的引數,如果是相應的依賴,你需要傳遞相應依賴的例項,接收的是array
的形式;
知道了以上的方法,你就可以自行嘗試反向解析某一個class
,然後分析出其從屬依賴,然後返回一個自動構建依賴的class
例項
本作品採用《CC 協議》,轉載必須註明作者和本文連結