Important notes
This is only because of "academical" interest, in real situation you should not care about - from where method was derived as it contradicts the idea of traits, e.g. transparent substitution.
Also, because of how traits are working, any kind of such manipulations might be considered as "hacky", so behavior may differ across different PHP versions and I would not suggest to rely on that.
Distinction: difficulties
In reflection for PHP, there is getTraits()
methods which will return ReflectionClass
instance, pointing to reflection of trait. This may be used to fetch all methods, declared in traits, which are used in the class. However - no, it will not help in your question as there will be not possible to distinct which methods were then overridden in the class.
Imagine that there is trait X
with methods foo()
and bar()
and there is class Z
with method bar()
. Then you will be able to know that methods foo()
and bar()
are declared in trait, but if you will try to use getMethods()
on class Z
- you will obviously get both foo()
and bar()
as well. Therefore, directly you can not distinct the case.
Distinction: work-aroud
However, yes, there is a way to still make it work. First way - is - like you've mentioned - try to investigate source code. It's quite ugly, but in the very end, this is the only 100% reliable way to resolve the matter.
But - no, there is another, "less ugly" way - to inspect instances on ReflectionMethod
classes, that are created for class/traits methods. It happens that PHP will use same instance for trait method, but will override that one which is for the method, declared in class.
This "inspection" can be done with spl_object_hash()
. Simple setup:
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()';
}
}
And now, to fetch hashes for both cases:
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()));
}
In short: it just fetches all methods from class trait (first function) or class itself (second function) and then merges results to get key=>value
map where key is object hash and value is method name.
Then we need to use that on same instance like this:
$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));
So result, $traitOnlyMethods
will contain only those methods, which are derived from trait.
The corresponding fiddle is here. But pay attention to results - they may be different from version to version, like in HHVM it just doesn't work (I assume because of how spl_object_hash
is implemented - an either way, it is not safe to rely on it for object distinction - see documentation for the function).
So, TD;DR; - yes, it can be (somehow) done even without source code parsing - but I can not imagine any reason why it will be needed as traits are intended to be used to substitute code into the class.