Laravel自动依赖解析的实现,其实是PHP映射解析

112 阅读3分钟

我们都知道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 =

class=parameter->getClass(), 可以进一步通过 $class->name 获取其 class 名称,然后就可以相应地去构建依赖实例了。

跟自动构建实例相关的其他方法:

isInstantiable() :判断一个 Class 或者传参能否被实例化,比如 interface 和 abstract class 就不能被实例化,这个一般用在进行反向解析最开始的地方,比如如果不能实例化,也就没必要去获取其 constructor 相关信息了;

newInstanceArgs() :基于你传递的参数来创建一个新的实例,这里传进去的参数,也就是 constructor 里需要传进去的参数,如果是相应的依赖,你需要传递相应依赖的实例,接收的是 array 的形式;

知道了以上的方法,你就可以自行尝试反向解析某一个 class,然后分析出其从属依赖,然后返回一个自动构建依赖的 class 实例。