PHP反射:如何知道方法/属性/常量是否从特征继承?

9 php reflection inheritance traits

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

是的,我可以这样检查:

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

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

don*_*ote 6

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

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

对于特征、类和方法都在同一条线上的奇特情况..拜托!


Alm*_* Do 0

重要笔记

这只是因为“学术”兴趣,在实际情况中您不应该关心 - 方法是从哪里派生的,因为它与特征的概念相矛盾,例如透明替代。

此外,由于特征的工作方式,任何类型的此类操作都可能被视为“hacky”,因此不同 PHP 版本的行为可能有所不同,我不建议依赖于此。

区别:困难

在 PHP 的反射中,有一些getTraits()方法会返回ReflectionClass实例,指向特征的反射。这可用于获取在特征中声明并在类中使用的所有方法。但是 - 不,这对您的问题没有帮助,因为不可能区分类中随后覆盖了哪些方法。

想象一下,有一个X带有方法的特质foo(),并且bar()有一个Z带有方法的类bar()。然后你将能够知道方法foo()bar()是在特征中声明的,但是如果你尝试getMethods()在类上使用Z- 你显然会同时得到foo()bar()。因此,您无法直接区分情况。

区别:解决方法

然而,是的,有一种方法仍然可以让它发挥作用。第一种方法 - 就像你提到的 - 尝试调查源代码。这很丑陋,但最终,这是解决问题的唯一 100% 可靠的方法。

但是 - 不,还有另一种“不太难看”的方法 - 检查为类/特征方法创建的类实例。ReflectionMethodPHP 可能会为特征方法使用相同的实例,但会覆盖在类中声明的方法的实例。

这种“检查”可以通过 来完成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()';
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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()));
}
Run Code Online (Sandbox Code Playgroud)

简而言之:它只是从类特征(第一个函数)或类本身(第二个函数)中获取所有方法,然后合并结果以获取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));
Run Code Online (Sandbox Code Playgroud)

因此,结果$traitOnlyMethods将仅包含从特征派生的那些方法。

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

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