PHP反射:如何知道方法/属性/常量是否继承自trait


PHP Reflection: How to know if a method/property/constant is inherited from trait?

我想从列表中的trait中排除所有继承的方法,这些方法在类中没有被覆盖那么,如何知道一个类成员是否是从特质遗传来的呢?

是的,我可以这样检查:

    if ($trait->hasMethod($methodName)
        || $ref->getTraitAliases()[$methodName] !== null)
    {
        //
    }

但是,如果trait方法在类中被否决了呢?怎么知道?一种方法是检查方法体是否相似,如果是,我可以排除它,但有更好的方法来实现这一点吗?

一种更简单的方法是ReflectionMethod::getFileName()。这将返回特征的文件名,而不是类。

对于trait和class在同一个文件中的奇异情况,可以使用ReflectionMethod::getStartLine(),并将其与trait和class的开始行和结束行进行比较。

对于特质、类和方法都在同一条线上的奇异情况。。哦,求你了!

很抱歉,Alma Do接受的答案是完全错误的。

即使解决了spl_object_hash((值被回收的问题,此解决方案也无法运行。这个问题可以通过将get*MethodRefs()函数重构为一个函数来克服,该函数计算两个结果,并确保在创建类方法的类似对象时,特征方法的ReflectionMethod对象仍然存在。这将阻止spl_object_hash((值的回收。

问题是,"PHP将为trait方法使用相同的实例"的假设是完全错误的,而这种情况的出现正是由于"幸运"的spl_object_hash((循环。$traitRef->getMethod('someName')返回的对象将始终与$classRef->getMethod('someName')返回的对象不同,->getMethods()返回的集合中ReflectionMethod的相应实例也将不同,无论方法someName()是否在类中被覆盖。这些对象不仅是不同的,甚至不会是"相等的":从$traitRef获得的ReflectionMethod实例将以特征的名称作为其class属性的值,而从$classRef获得的实例将在那里具有类的名称。

Fiddle:https://3v4l.org/CqEW3

看来只有基于解析器的方法才是可行的。

重要注意事项

这只是因为"学术"兴趣,在真实情况下你不应该关心——方法是从哪里衍生出来的,因为它与特征的概念相矛盾,例如透明替代。

此外,由于特性是如何工作的,任何类型的此类操作都可能被视为"黑客",因此不同PHP版本的行为可能不同,我不建议依赖这一点。

区别:困难

在PHP的反射中,有getTraits()方法将返回ReflectionClass实例,指向trait的反射。这可以用于获取类中使用的在traits中声明的所有方法。然而,不,这对你的问题没有帮助,因为不可能区分哪些方法随后在类中被重写。

假设方法foo()bar()具有特征X,方法bar()具有类Z。然后,您将能够知道方法foo()bar()是在trait中声明的,但如果您尝试在类Z上使用getMethods(),您显然也会得到foo()bar()。因此,你不能直接区分这种情况。

区别:围绕工作

然而,是的,有一种方法仍然可以让它发挥作用。第一种方法是——正如您所提到的——尝试研究源代码。这很丑陋,但最终,这是解决问题的唯一100%可靠的方法。

但是-不,还有另一种"不那么难看"的方法-检查ReflectionMethod类上的实例,这些实例是为类/特征方法创建的。PHP会为trait方法使用相同的实例,但会覆盖类中声明的用于该方法的实例。

这个"检查"可以用spl_object_hash()来完成。简单设置:

trait x
{
    public function foo()
    {
        echo 'Trait x foo()';
    }
    public function bar()
    {
        echo 'Trait x bar()';
    }
}
class z
{
    use x;
    public function foo()
    {
        echo 'Class foo()';
    }
}

现在,要获取这两种情况的哈希:

function getTraitMethodsRefs(ReflectionClass $class)
{
    $traitMethods = call_user_func_array('array_merge', array_map(function(ReflectionClass $ref) {
        return $ref->getMethods();
    }, $class->getTraits()));
    $traitMethods = call_user_func_array('array_merge', array_map(function (ReflectionMethod $method) {
        return [spl_object_hash($method) => $method->getName()];
    }, $traitMethods));
    return $traitMethods;    
}
function getClassMethodsRefs(ReflectionClass $class)
{
    return call_user_func_array('array_merge', array_map(function (ReflectionMethod $method) {
        return [spl_object_hash($method) => $method->getName()];
    }, $class->getMethods()));
}

简而言之:它只是从类trait(第一个函数(或类本身(第二个函数(中获取所有方法,然后合并结果以获得key=>value映射,其中键是对象哈希,值是方法名称。

然后我们需要在同一个实例上使用它,如下所示:

$obj = new z;
$ref = new ReflectionClass($obj);
$traitRefs   = getTraitMethodsRefs($ref);
$classRefs   = getClassMethodsRefs($ref);
$traitOnlyHashes = array_diff(
    array_keys($traitRefs),
    array_keys($classRefs)
);
$traitOnlyMethods = array_intersect_key($traitRefs, array_flip($traitOnlyHashes));

因此,$traitOnlyMethods将只包含那些来源于性状的方法。

相应的小提琴在这里。但要注意结果——它们可能因版本而异,就像在HHVM中一样,它根本不起作用(我认为是因为spl_object_hash是如何实现的——无论哪种方式,依赖它来区分对象都是不安全的——请参阅函数文档(。

所以,TD;DR-是的,即使没有源代码解析,它也可以(以某种方式(完成-但我无法想象为什么需要它,因为特性是用来将代码替换到类中的。