反射API之于PHP,就如同java.lang.reflect包之于java。反射API包含用于分析属性、方法和类的内置类。反射API还可以与访问权限控制、接口和抽象类等PHP的面向对象特性协作。

ReflectionClass类为我们提供了检查给定类(包括用户定义的类和PHP的内置类)的所有信息的方法。ReflectionClass的构造方法接收一个类或接口名(或一个对象实例)作为其唯一参数。

<?php
//------------------------
// php 服务器500错误解决
// 开启php.ini中的display_errors指令
ini_set('display_errors',1);
// 通过error_reporting()函数设置,输出所有级别的错误报告
error_reporting(E_ALL);
//------------------------

// 自动加载
$basic = function($classname)
{
    // 当前文件夹下
    $file = __DIR__ . "/" . "{$classname}.php";
    if(file_exists($file))
    {
        require_once($file);
    }
};
\spl_autoload_register($basic);

$prodclass = new \ReflectionClass('BookProduct');
\Reflection::export($prodclass);

创建一个ReflectionClass对象后,我们可以用Reflection工具类输出CdProduct的信息。Reflection的静态方法export可以格式化输出Reflection对象(也就是说,实现Reflector接口的类的任何实例)所管理的数据。

检查类

Reflection::export()方法可以为调试提供大量有用的信息,但我们还可以用反射API做更多专业的事情。接下来我们试试Reflection类。

前面我们已经探讨过如何实例化一个ReflectionClass对象

$prodclass = new \ReflectionClass('BookProduct');

接下来用这个ReflectionClass对象在脚本中检查CdProduct。它究竟是一个什么样的类。创建一个ClassInfo.php文件,内容如下

<?php
//------------------------
// php 服务器500错误解决
// 开启php.ini中的display_errors指令
ini_set('display_errors',1);
// 通过error_reporting()函数设置,输出所有级别的错误报告
error_reporting(E_ALL);
//------------------------

// 自动加载
$basic = function($classname)
{
    // 当前文件夹下
    $file = __DIR__ . "/" . "{$classname}.php";
    if(file_exists($file))
    {
        require_once($file);
    }
};
\spl_autoload_register($basic);

class ClassInfo
{
    public static function getData(\ReflectionClass $class)
    {
        $details = "";
        // 返回要检查的类的名称
        $name = $class->getName();
        // 是否在PHP代码中声明的类(用户定义的)
        if($class->isUserDefined())
        {
            $details .= "$name is user defined <br/>";
        }
        // 检查类是否为PHP的内置类
        if($class->isInternal())
        {
            $details .= "$name is built-in <br/>";
        }
        // 检查类是否为接口
        if($class->isInterface())
        {
            $details .= "$name is interface <br/>";
        }
        // 检查类是否为抽象类
        if($class->isAbstract())
        {
            $details .= "$name is an abstract class <br/>";
        }
        if($class->isFinal())
        {
            $details .= "$name is a final class <br/>";
        }
        // 检查类是否可以实例化
        if($class->isInstantiable())
        {
            $details .= "$name can be instantiated <br/>";
        }
        else
        {
            $details .= "$name can not be instantiated <br/>";
        }
        // 检查类是否可以克隆
        if($class->isCloneable())
        {
            $details .= "$name can be cloned <br/>";
        }
        else
        {
            $details .= "$name can not be cloned <br/>";
        }
        return $details;
    }
}

$prodclass = new \ReflectionClass('CdProduct');
print ClassInfo::getData($prodclass);

结果:

CdProduct is user defined
CdProduct can be instantiated
CdProduct can be cloned 

甚至还可以用ReflectionClass对象来检查用户自定义类的源码。ReflectionClass对象可以告诉我们定义类的文件名,及其内部的第一行和最后一行。以下是一种通过ReflectionClass访问类源码的简单粗暴的方法:

<?php
//------------------------
// php 服务器500错误解决
// 开启php.ini中的display_errors指令
ini_set('display_errors',1);
// 通过error_reporting()函数设置,输出所有级别的错误报告
error_reporting(E_ALL);
//------------------------

// 自动加载
$basic = function($classname)
{
    // 当前文件夹下
    $file = __DIR__ . "/" . "{$classname}.php";
    if(file_exists($file))
    {
        require_once($file);
    }
};
\spl_autoload_register($basic);

class ReflectionUtil
{
    public static function getClassSource(\ReflectionClass $class): string
    {
        $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')
);

ReflectionUtil是一个只有静态方法ReflectionUtil::getClassSource()的简单类。该方法接收一个ReflectionClass对象作为唯一参数,并返回类的源码。ReflectionClass::getFileName()会返回类的绝对路径,这样我们就可以访问类文件所处的路径,并打开文件。file()返回一个数组,其中包含文件中的所有行。ReflectionClass::getStartLine()会返回类定义的第一行,ReflectionClass::getEndLine()则是最后一行。如此,我们只需要简单地调用array_slice()方法,就可以从数组中提取类的定义。为了保持代码简洁,本例中省略了错误处理。在实际项目中,我们还应该检查参数和返回值。

检查方法

就像ReflectionClass可以检查类一样,ReflectionMethod对象可以检查方法。可以通过两种方式得到ReflectionMethod对象。第一种方式是通过ReflectionClass::getMethods()得到一个ReflectionMethod对象的数组;第二种方式是调用一个特殊的ReflectionClass::getMehod()方法,它接收一个方法名作为参数并返回相应的ReflectionMethod对象。

接下来我们用ReflectionClass::getMethods()获取对象中的所有方法,并用ReflectionMethod类逐一查看这些方法的详细信息。

<?php
//------------------------
// php 服务器500错误解决
// 开启php.ini中的display_errors指令
ini_set('display_errors',1);
// 通过error_reporting()函数设置,输出所有级别的错误报告
error_reporting(E_ALL);
//------------------------

// 自动加载
$basic = function($classname)
{
    // 当前文件夹下
    $file = __DIR__ . "/" . "{$classname}.php";
    if(file_exists($file))
    {
        require_once($file);
    }
};
\spl_autoload_register($basic);

class ClassInfo
{
    public static function getData(\ReflectionClass $class)
    {
        $details = "";
        // 返回要检查的类的名称
        $name = $class->getName();
        // 是否在PHP代码中声明的类(用户定义的)
        if($class->isUserDefined())
        {
            $details .= "$name is user defined <br/>";
        }
        // 检查类是否为PHP的内置类
        if($class->isInternal())
        {
            $details .= "$name is built-in <br/>";
        }
        // 检查类是否为接口
        if($class->isInterface())
        {
            $details .= "$name is interface <br/>";
        }
        // 检查类是否为抽象类
        if($class->isAbstract())
        {
            $details .= "$name is an abstract class <br/>";
        }
        if($class->isFinal())
        {
            $details .= "$name is a final class <br/>";
        }
        // 检查类是否可以实例化
        if($class->isInstantiable())
        {
            $details .= "$name can be instantiated <br/>";
        }
        else
        {
            $details .= "$name can not be instantiated <br/>";
        }
        // 检查类是否可以克隆
        if($class->isCloneable())
        {
            $details .= "$name can be cloned <br/>";
        }
        else
        {
            $details .= "$name can not be cloned <br/>";
        }
        return $details;
    }

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

$prodclass = new \ReflectionClass('CdProduct');
$methods = $prodclass->getMethods();

foreach($methods as $method)
{
    print ClassInfo::methodData($method);
    print "---------------------<br/>";
}

这段代码用ReflectionClass::getMethods()得到一个ReflectionMethod对象的数组,并接着遍历这个数组,以每个ReflectionMethod对象作为参数调用methodData()methodData()中用到的方法名反映了其设计意图:这段代码会分别检查这些方法是否为用户自定义的、内置的、抽象的、public的、protected的、静态的或final的。我们还可以检查这些方法是否为其类的构造方法,或它们是否返回了引用。

注意,如果被检查的方法简单地返回一个对象,那么即使PHP5中对象是引用传递和赋值的,ReflectionMethod::returnsReference()也不会返回true。只有被检查的方法明确地声明返回引用(方法名称前有个&符号)时,ReflectionMethod::returnsReference()才会返回true

同样,我们也可以使用ReflectionMethod来获取方法地源代码:

<?php
//------------------------
// php 服务器500错误解决
// 开启php.ini中的display_errors指令
ini_set('display_errors',1);
// 通过error_reporting()函数设置,输出所有级别的错误报告
error_reporting(E_ALL);
//------------------------

// 自动加载
$basic = function($classname)
{
    // 当前文件夹下
    $file = __DIR__ . "/" . "{$classname}.php";
    if(file_exists($file))
    {
        require_once($file);
    }
};
\spl_autoload_register($basic);

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

    public static function getMethodSource(\ReflectionMethod $method): string
    {
        $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);

检查方法参数

现在我们已经可以在方法签名中限制对象参数的类型了,因此,检查方法签名中声明的参数功能就变得非常有用。为此,反射API提供了一个ReflectionParameter类。要想得到ReflectionParameter对象,就需要借助ReflectionMethod对象。ReflectionMethod::getParameters()方法会返回一个包含ReflectionParamete对象的数组。ReflectionParamete不仅可以表明参数名及变量是否引用传递(即方法声明前是否有&符号),还可以表明参数提示所要求的类型,以及方法是否接收一个null值参数。

<?php
//------------------------
// php 服务器500错误解决
// 开启php.ini中的display_errors指令
ini_set('display_errors',1);
// 通过error_reporting()函数设置,输出所有级别的错误报告
error_reporting(E_ALL);
//------------------------

// 自动加载
$basic = function($classname)
{
    // 当前文件夹下
    $file = __DIR__ . "/" . "{$classname}.php";
    if(file_exists($file))
    {
        require_once($file);
    }
};
\spl_autoload_register($basic);

class ClassInfo
{
    public static function getData(\ReflectionClass $class)
    {
        $details = "";
        // 返回要检查的类的名称
        $name = $class->getName();
        // 是否在PHP代码中声明的类(用户定义的)
        if($class->isUserDefined())
        {
            $details .= "$name is user defined <br/>";
        }
        // 检查类是否为PHP的内置类
        if($class->isInternal())
        {
            $details .= "$name is built-in <br/>";
        }
        // 检查类是否为接口
        if($class->isInterface())
        {
            $details .= "$name is interface <br/>";
        }
        // 检查类是否为抽象类
        if($class->isAbstract())
        {
            $details .= "$name is an abstract class <br/>";
        }
        if($class->isFinal())
        {
            $details .= "$name is a final class <br/>";
        }
        // 检查类是否可以实例化
        if($class->isInstantiable())
        {
            $details .= "$name can be instantiated <br/>";
        }
        else
        {
            $details .= "$name can not be instantiated <br/>";
        }
        // 检查类是否可以克隆
        if($class->isCloneable())
        {
            $details .= "$name can be cloned <br/>";
        }
        else
        {
            $details .= "$name can not be cloned <br/>";
        }
        return $details;
    }

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

    public static function argData(\ReflectionParameter $arg)
    {
        $details = "";
        $declaringclass = $arg->getDeclaringClass();
        $name = $arg->getName();
        $class = $arg->getClass();
        $position = $arg->getPosition();
        $details .= "\$$name has position $position <br/>";
        if(! empty($class))
        {
            $classname = $class->getName();
            $details .= "\$$name must be a $classname object <br/>";
        }
        if($arg->isPassedByReference())
        {
            $details .= "\\$$name is passed by reference <br/>";
        }
        if($arg->isDefaultValueAvailable())
        {
            $def = $arg->getDefaultValue();
            $details .= "\$$name has default:$def <br/>";
        }
        if($arg->allowsNull())
        {
            $details .= "\$$name can be null <br/>";
        }
        return $details;
    }
}

$class = new \ReflectionClass('CdProduct');
$method = $class->getMethod("__construct");
$params = $method->getParameters();
foreach($params as $param)
{
    print ClassInfo::argData($param) . "<br/>";
}

这段代码先调用ReflectionClass::getMethod()方法得到了一个ReflectionMethod对象,接着调用ReflectionMethod::getParameters()得到由ReflectionParameter对象组成的数组,然后argData()函数用接收到的ReflectionParameter对象获取参数的相关信息。argData函数首先通过ReflectionParameter::getName()得到参数名。如果方法声明中有类型提示,那么ReflectionParameter::getClass()方法会返回一个ReflectionClass对象。接着该函数用isPassedByReference()方法检查参数是否为引用。最后,它还会检查参数是否提供了默认值,如果存在默认值,则将默认值拼接到字符串中一同返回。

标签: none

评论已关闭