PHP instanceof for traits

24,022

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));
}
Share:
24,022
xpedobearx
Author by

xpedobearx

Updated on October 07, 2021

Comments

  • xpedobearx
    xpedobearx over 2 years

    What is the proper way to check if a class uses a certain trait?

  • lazycommit
    lazycommit over 7 years
    This 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
    Kafoso about 7 years
    The question is about traits, which cannot implement interfaces. The above snippet will only work for classes.
  • Armin
    Armin over 5 years
    This 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
    Jeremy Wadhams over 5 years
    Note: 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
    edigu over 4 years
    Having 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 the instanceof FooInterface approach suggested above, even it has a downside with non-public methods.
  • J. D.
    J. D. over 4 years
    IMHO, 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
    Kamafeather over 3 years
    I 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
    Lupinity Labs about 3 years
    Of course you may also choose to use abstract trait members in place of interfaces (see php.net/manual/en/…).