反射API

反射API由一系列可以分析属性、方法和类的内置类组成。

  • 入门

反射API不仅仅被用于检查类。例如,ReflectionFunction类提供了关于给定函数的信息,ReflectionExtension类可以查看编译到PHP语言中的扩展。利用反射API的这些类,可以在运行时访问对象、函数和脚本中的扩展的信息。反射的另一用途是根据命名规则创建一个调用模板类中方法的框架。

  • 开始行动

ReflectionClass提供揭示给定类所有信息的方法,无论这个类是用户定义的还是PHP自带的内置类。

$prod_class = new ReflectionClass('CdProduct');
Reflection::export($prod_class);

该函数与调试函数var_dump()相比较,var_dump()函数是汇总数据的通用工具,但使用前必须先实例化一个对象,而且无法提供想Reflection::export()提供的那么多细节。

$cd = new CdProduct("cd1", "bob", "bobbleson", 4, 50);
var_dump($cd);

var_dump()和它的姊妹函数print_r()是检测PHP代码中数据的利器,但对于类和函数,反射API提供了更高层次的功能。

  • 检查类

接着,让我们使用ReflectionClass对象来研究脚本中的CDProduct。这个类属于哪一种类型?可以创建实例吗?下面这个函数回答了这些问题:

function classData(ReflectionClass $class)
{
    $details = "";
    $name = $class->getName();
    if ($class->isUserDefined()) {
        $details .= "$name is user defined\n";
    }
    if ($class->isInternal()) {
        $details .= "$name is built-in\n";
    }
    if ($class->isAbstract()) {
        $details .= "$name is an abstract class\n";
    }
    if ($class->isInterface()) {
        $details .= "$name is interface\n";
    }
    if ($class->isFinal()) {
        $details .= "$name is a final class\n";
    }
    if ($class->isInstantiable()) {
        $details .= "$name can be instantiated\n";
    } else {
        $details .= "$name can not be instantiated\n";
    }
    return $details;
}

$prod_class = new ReflectionClass(('CdProduct'));
print classData($prod_class);

你甚至可以检查用户自定义的相关源代码。ReflectionClass对象提供自定义类所在的文件名及文件中类的起始和终止行。

class ReflectionUtil
{
    static function getClassSource(ReflectionClass $class)
    {
        $path = $class->getFileName();
        $lines = @file($path);
        $from = $class->getStartLine();
        $to = $class->getEndLine();
        $len = $to - $from + 1;
        return implode(array_slice($lines, $from - 1, $len));
    }
}

print ReflectionUtil::getClassSource(new ReflectionClass('CdProduct'));

在实际项目中,应该检查参数和代码结果。

  • 检查方法
$prod_class = new ReflectionClass('CdProduct');
$methods = $prod_class->getMethods();

foreach ($methods as $method) {
    print methodData($method);
    print "\n----\n";
}

function methodData(ReflectionMethod $method)
{
    $details = "";
    $name = $method->getName();
    if ($method->isUserDefined()) {
        $details .= "$name is user defined\n";
    }
    if ($method->isInternal()) {
        $details .= "$name is built-in\n";
    }
    if ($method->isAbstract()) {
        $details .= "$name is an abstract class\n";
    }
    if ($method->isPublic()) {
        $details .= "$name is public\n";
    }
    if ($method->isProtected()) {
        $details .= "$name is protected\n";
    }
    if ($method->isPrivate()) {
        $details .= "$name is private\n";
    }
    if ($method->isStatic()) {
        $details .= "$name is static\n";
    }
    if ($method->isFinal()) {
        $details .= "$name is final\n";
    }
    if ($method->isConstructor()) {
        $details .= "$name is the constructor\n";
    }
    if ($method->returnsReference()) {
        $details .= "$name returns a reference (as opposed to a value)\n";
    }
    return $details;
}

可以像之前那样使用ReflectionClass那样获得类方法的源代码。

class ReflectionUtil
{
    static function getMethodSource(ReflectionMethod $method)
    {
        $path = $method->getFileName();
        $lines = @file($path);
        $from = $method->getStartLine();
        $to = $method->getEndLine();
        $len = $to - $from + 1;
        return implode(array_slice($lines, $from - 1, $len));
    }
}

$class = new ReflectionClass('CdProduct');
$method = $class->getMethod('getSummaryLine');
print ReflectionUtil::getMethodSource($method);
  • 检查方法参数

在PHP5中,声明类方法时可以限制参数中对象的类型,因此检查方法的参数变得非常必要。为此,反射API提供了ReflectionParameter类。

$class = new ReflectionClass('CdProduct');
$method = $class->getMethod('__construct');
$params = $method->getParameters();

foreach ($params as $param) {
    print argData($param) . "\n";
}

function argData(ReflectionParameter $arg)
{
    $details = "";
    $declaringclass = $arg->getDeclaringClass();
    $name = $arg->getName();
    $class = $arg->getClass();
    $position = $arg->getPosition();
    $details .= "\$$name has position $position\n";
    if (!empty($class)) {
        $classname = $class->getName();
        $details .= "\$$name must be a $classname object\n";
    }
    if ($arg->isPassedByReference()) {
        $details .= "\$$name is passed by reference\n";
    }
    if ($arg->isDefaultValueAvailable()) {
        $def = $arg->getDefaultValue();
        $details .= "\$$name has default:$def\n";
    }
    return $details;
}
  • 使用反射API

假设我们要创建一个类来动态调用Module对象,即该类可以自由加载第三方插件并集成进已有的系统,而不需把第三方代码硬编码进原有的代码。要达到这个目的,可以在module接口或抽象类中定义一个execute()方法,强制要求所有的子类必须实现该方法。可以允许用户在外部XML配置文件中列出所有Module类。系统可以使用XML提供的信息来加载一定数目的Module对象,然后对每个Module对象调用execute()。

class Person
{
    public $name;

    function __construct($name)
    {
        $this->name = $name;
    }
}

interface Module
{
    function execute();
}

class FtpModule implements Module
{
    function setHost($host)
    {
        print "FtpModule::setHost():$host\n";
    }

    function setUser($user)
    {
        print "FtpModule::setUser():$user\n";
    }

    function execute()
    {
    }
}

class PersonModule implements Module
{

    function setPerson(Person $person)
    {
        print "PersonModule::setPerson():{$person->name}\n";
    }

    function execute()
    {
    }

}

class ModuleRunner
{
    private $configData = array(
        "PersonModule" => array('person' => 'bob'),
        "FtpModule" => array('host' => 'example.com',
            'user' => 'anon')
    );

    private $modules = array();

    function init()
    {
        $interface = new ReflectionClass('Module');
        foreach ($this->configData as $modulename => $params) {
            $module_class = new ReflectionClass($modulename);
            if (!$module_class->isSubclassOf($interface)) {
                throw new Exception("unknown module type:$modulename");
            }
            $module = $module_class->newInstance();
            foreach ($module_class->getMethods() as $method) {
                $this->handleMethod($module, $method, $params);
                //
            }
            array_push($this->modules, $module);
        }
    }

    function handleMethod(Module $module, ReflectionMethod $method, $params)
    {
        $name = $method->getName();
        $args = $method->getParameters();

        if (count($args) != 1 || substr($name, 0, 3) != "set") {
            return false;
        }

        $property = strtolower(substr($name, 3));
        if (!isset($params[$property])) {
            return false;
        }

        $arg_class = $args[0]->getClass();
        if (empty($arg_class)) {
            $method->invoke($module, $params[$property]);
        } else {
            $method->invoke($module, $arg_class->newInstance($params[$property]));
        }
    }
}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐