PHP中的反射API
反射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()方法检查参数是否为引用。最后,它还会检查参数是否提供了默认值,如果存在默认值,则将默认值拼接到字符串中一同返回。
评论已关闭