反射机制被 Ruby、PHP 等多种语言广泛使用,主要用来动态地获取系统中类、实例对象、方法等语言构件的信息,通过反射 API 函数可以实现对这些语言构件信息的动态获取和动态操作等。PHP 5 具有完整的反射 API ,添加了对类、接口、函数、方法和扩展进行反向工作的能力。此外,反射 API 还提供了获取函数、类和方法等语言构件中的文档注释的方法。

下面介绍一个具体的实例。

1
2
3
4
5
6
7
8
9
10
11
<?php
class A {
public function call ()
{
echo "Hello wshuo";
}
}
$ref = new ReflectionClass('A');
$inst = $ref->newInstanceArgs();
$inst->call();
输出: Hello wshuo

通过以上的实例可以看到反射机制的强大,在很多情况下可以使得代码更加高效,例如事先不知道需要实例化哪个类,而是在运行是根据动态信息确定,对于这种情况可以同过反射机制获取需要实例化类的构造函数信息并完成相应的实例化。在 Laravel 框架中,服务容器解析服务的过程中就用到了反射机制。下面给出了 Laravel 框架中解析服务的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
文件:Illuminate\Container\Container.php
// 根据给定的类初始化一个具体实例
public functin build ($concrete, array $parameters = [] )
{
// 如果是匿名函数直接解析
if ($concrete instanceof Closure) {
return $concrete($this, $parameters);
}
// 创建一个反射类实例
$reflector = new ReflectionClass($concrete);
// 判断是否可以被例化,如果不可以则抛出异常
if (! $reflector->isInstantiable()) {
$message = "Target [$concrete] is not instantiable.";
throw new BindingResolutionContractException($message);
}
$this->buildStack[] = $concrete;
// 获取类的构造方法。当该类存在构造函数是返回一个 `ReflectionMethod` 对象,相当于获取构造函数的反射类
$constructor = $reflector->getConstructor();
if (is_null($constructor)) {
// 判断是否存在构造函数,如果不存在则直接实例化该类
array_pop($this->buildStack);
return new $concrete;
}
// 获取构造函数依赖的输入参数
$dependencies = $constructor->getParameters();
// 获取直接提供的实参。
$parameters = $this->keyParametersByargument($dependencies, $parameters);
$instances = $this->getDependencies($dependencies, $parameters);
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
}

在 Laravel 框架中,解析服务是通过 build() 函数实现的,一般分为两种情况:

  • 一种是查找对应服务是否被服务提供者注册为实例或者提供服务的匿名函数,如果是,则直接进行服务解析;
  • 第二种是服务名称没有相对应的服务绑定,通过反射机制来动态创建服务。通过反射机制动态创建服务的过程可以分两个步骤:
    • 第一步是通过反射机制获取服务类构造函数的信息。
    • 第二步是解决服务构造类的依赖问题。
      • 首先,通过 $reflector = new ReflectionClass($concrete); 来创建一个反射类实例,其中 $concrete 是类的名称。
      • 然后通过 $reflector->isInstantiable() 判断这个类是否可以实例化,如果不可以则抛出异常,
      • 接着通过 $constructor = $reflector->getConstructor() 来获取类的构造方法。当该类存在构造函数是返回一个 ReflectionMethod 对象,相当于获取构造函数的反射类,
        • 当不存在构造函数是返回 NULL,最后通过 is_null($constructor) 判断是否存在构造函数,如果不存在则直接实例化该类,
        • 如果存在则通过 $dependencies = $constructor->getParameters() 来获取构造函数依赖的输入参数。

下面将解决构造函数中依赖参数的问题,进而实现依赖注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
文件:Illuminate\Contrainer\Contriner.php
//根据反射参数解决所有的参数依赖
protected function getDependencies (array $parameters, array $primitives = [])
{
$dependencies = [];
foreach ($parameters as $parameter) {
$dependency = $parameter->getClass();
if (array_key_exists($parameter->name, $primitives)) {
$dependencies[] = $primitives[$parameter->name];
} elseif (is_null($dependency)) {
$dependencies[] = $this->resolveNonClass($parameter);
} else {
$dependencies[] = $this->resolveClass($parameter);
}
}
return (array) $denpendencies;
}
// 解决无法获取类名的依赖
protected function resolveNonClass(ReflectionParameter $parameter)
{
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}
// 省略异常处理代码
}
// 通过服务容器解决一个具有类名的依赖
protected function resolveClass(ReflectionParameter $parameter)
{
try {
return $this->make($parameter->getClass()->name);
}
// 省略异常处理部分代码
}

依赖和依赖入的概念将会在 Laravel 框架中的设计模式章节介绍,这里可以简绝大部分的理解为获取类构造函数中的参数,进而完成类的实例化。

  • 首先,通过 $parameters = $this->keyParametersByArgument($dependencies, $parameters); 获取直接提供的实参。
  • 未直接提供的通过 $instances = $this->getDependencies($denpendencies,$parameters);根据形参是类型取实参。
    getDependencies() 函数中需要调用 resolveNonClass() 函数或 resolveClass() 函数解决参数依赖问题。
    对于构造函数的参数,如果无法获取该参数的类名,则通过 resolveNonClass() 函数获取默认的参数值,
    如果可以获取类的名称,则通过 resolveClass() 函数进行实例化,而实例化过程是通过服务容器进行解析的,即通过$parameter->getClass()->name 来获取参数的类名,
    然后通过 $this->make($parameter->getClass()->name;来解析服务,make() 函数在接下来还会调用 build() 函数完成类的实例化过程,相当于一个递归调用的过程,
    最终有 $reflector->newInstanceArgs($instances);实例化服务类,进而完成服务的解析。

这部分内容设计许多新的概念,如依赖注入,服务容器、服务解析等,这些概念将会在后面的章节进行详细介绍,这里的读者只需要对反射机制有个了解就可以,随着介绍的深入会真正理解其本质。