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

Laravel框架
431
0
0
2022-05-09

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;

    //Constructorpublic 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实例