I want to exclude all inherited methods from trait(s) from the list that are not overriden in a class So how to know if a class member is inherited from trait?
Yes, I can check it like this:
if ($trait->hasMethod($methodName)
|| $ref->getTraitAliases()[$methodName] !== null)
{
//
}
But what if the trait method is overriden in a class? How to know it? One way is to check if method bodies are similar, if so, i may exclude it, but is there a better way to achieve this?
A simpler way to do this is
ReflectionMethod::getFileName()
. This will return the file name of the trait, not the class.For the exotic case where trait and class are in the same file, one can use
ReflectionMethod::getStartLine()
, and compare this with start and end line of trait and class.For the exotic case where trait and class and method are all on the same line.. oh please!
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 returnReflectionClass
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 methodsfoo()
andbar()
and there is classZ
with methodbar()
. Then you will be able to know that methodsfoo()
andbar()
are declared in trait, but if you will try to usegetMethods()
on classZ
- you will obviously get bothfoo()
andbar()
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:And now, to fetch hashes for both cases:
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:
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.
I am sorry but the accepted answer by Alma Do is completely wrong.
This solution cannot work even if you overcome the problem of spl_object_hash() values being recycled. This problem can be overcome by refactoring the
get*MethodRefs()
functions into one function that computes both results and ensures that theReflectionMethod
objects for the trait methods still exist when the analogous objects for the class methods are created. This prevents recycling of spl_object_hash() values.The problem is, the assumption that "PHP will use same instance for trait method" is completely false, and the appearance of that happening resulted precisely from "lucky" spl_object_hash() recycling. The object returned by
$traitRef->getMethod('someName')
will always be distinct from the object returned by$classRef->getMethod('someName')
, and so will be the corresponding instances ofReflectionMethod
in collections returned by->getMethods()
, regardless of whether methodsomeName()
is overriden in the class or not. These objects will not only be distinct, they won't even be "equal": theReflectionMethod
instance obtained from$traitRef
will have the name of the trait as the value of itsclass
property, and the one obtained from$classRef
will have the name of the class there.Fiddle: https://3v4l.org/CqEW3
It would seem that only parser-based approaches are viable then.