PHP instanceof for traits
Solution 1
While nothing stops you from using methods to determine if a class uses a trait, the recommended approach is to pair traits with interfaces. So you'd have:
class Foo implements MyInterface
{
use MyTrait;
}
Where MyTrait
is an implementation of MyInterface
.
Then you check for the interface instead of traits like so:
if ($foo instanceof MyInterface) {
...
}
And you can also type hint, which you can't do with traits:
function bar(MyInterface $foo) {
...
}
In case you absolutely need to know whether a class is using a certain trait or implementation, you can just add another method to the interface, which returns a different value based on the implementation.
Solution 2
You can use class_uses
function to get an array of all the traits used by a class.
Then you check if this array has a key with the same name of the trait you're testing for.
if so, then your class is using your trait. If not, then it's not using it.
Solution 3
It's not really clean and may not be the right solution for your case. But an alternative is to check if the object or class implements a method of the Trait (as usually you don't overwrite existing methods with Trait)
if (method_exists($my_object, 'MyTraitSpecificMethod')){
...
}
Solution 4
I just found how Laravel solves this and thought I'd share it here. It uses class_uses
beneath but goes through all the parents to find all the traits recursively.
It defines a helper function called class_uses_recursive:
function class_uses_recursive($class)
{
if (is_object($class)) {
$class = get_class($class);
}
$results = [];
foreach (array_reverse(class_parents($class)) + [$class => $class] as $class) {
$results += trait_uses_recursive($class);
}
return array_unique($results);
}
function trait_uses_recursive($trait)
{
$traits = class_uses($trait);
foreach ($traits as $trait) {
$traits += trait_uses_recursive($trait);
}
return $traits;
}
And you can use it like this:
in_array(MyTrait::class, class_uses_recursive($class));
You can see how they use it to check if a model implements the SoftDeletes trait here:
public function throughParentSoftDeletes()
{
return in_array(SoftDeletes::class, class_uses_recursive($this->throughParent));
}
xpedobearx
Updated on October 07, 2021Comments
-
xpedobearx over 2 years
What is the proper way to check if a class uses a certain trait?
-
lazycommit over 7 yearsThis approach is not useful in case of non-public logic implementation. When, for example, you need to produce different variants of behavior with easy
use BehaviorATrait; use BehaviorBTrait;
and it should be based on some protected methods interfaces. For sure it is not a good design but a talk of a tool flexibility. -
Kafoso about 7 yearsThe question is about traits, which cannot implement interfaces. The above snippet will only work for classes.
-
Armin over 5 yearsThis answer is in two ways wrong. First: It does not answer the original question, of how to check for used traits in objects.
class_uses
does the trick. However, to say that it is the "recommended approach" to create a redundant interface for the trait, is just wrong. Actually it raises the error-proneness, because when you forget to define both, Interface & Trait, it may occure unexpected behaviour. And on the other side, you don't need the flexibility to define interface & trait separately. So keep it simple! -
Jeremy Wadhams over 5 yearsNote: class_uses only tells you if the class implements the trait directly, not through inheritance. Check the comments on php.net for some ideas on rolling your own method that also walks the class's ancestry php.net/manual/en/function.class-uses.php
-
edigu over 4 yearsHaving a method call then looking up in an array of strings to be able to understand if a class uses a trait or not is too much work to do on runtime. It's much more expensive than checking via language constructs such as
instanceof
. I would recommend anyone following theinstanceof FooInterface
approach suggested above, even it has a downside with non-public methods. -
J. D. over 4 yearsIMHO, there is somehting wrong with @Armin s comment as well. Interface are always "redundant", by design. Yet, they do not raise error-proneness, but mitigate it, if applied correctly, because the developer can keep the information on what kind of object to expect in a single place, while staying open for various implementation details.
-
Kamafeather over 3 yearsI agree with @J.D. and would add that (imho) Traits can be (almost) safely used without Interface for simple cases. If you get used to use Traits and push them forward, one day or another you'll find yourself needing Interfaces, to guarantee implicit contracts and prevent buggy brittle code. Yes: it's redundant, but also most of the PHP brackets are, or having to type
$this
. Interfaces make sure you (and others) understand your code and makes your code more SOLID... ehm.. robust. -
Lupinity Labs about 3 yearsOf course you may also choose to use abstract trait members in place of interfaces (see php.net/manual/en/…).