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
也即这个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
实例