Laravel自動依賴解析的實現,其實是PHP對映解析

Laravel00發表於2021-07-04

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也即這個classname,相關的還有一個很明顯的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或者傳參能否被例項化,比如interfaceabstract class就不能被例項化,這個一般用在進行反向解析最開始的地方,比如如果不能例項化,也就沒必要去獲取其constructor相關資訊了;

newInstanceArgs() : 基於你傳遞的引數來建立一個新的例項,這裡傳進去的引數,也就是constructor裡需要傳進去的引數,如果是相應的依賴,你需要傳遞相應依賴的例項,接收的是array的形式;

知道了以上的方法,你就可以自行嘗試反向解析某一個class,然後分析出其從屬依賴,然後返回一個自動構建依賴的class例項

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章