34

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

Samuel Katz
  • 24,066
  • 8
  • 71
  • 57
xpedobearx
  • 707
  • 3
  • 8
  • 13
  • 17
    Does it? According to my knowledge it does not (`$x instanceof SomeTrait` will always be false). – kuba Oct 12 '16 at 12:13
  • 2
    Possible duplicate of [how to get used traits in php-class?](http://stackoverflow.com/questions/13633346/how-to-get-used-traits-in-php-class) – ar34z Nov 03 '16 at 08:19
  • 4
    `instanceof` is always false with a trait. traits are not any instance of something. – Raffaello Dec 12 '16 at 10:31

5 Answers5

36

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.

Cave Johnson
  • 6,499
  • 5
  • 38
  • 57
Kuba Birecki
  • 2,926
  • 1
  • 13
  • 16
  • 3
    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. – lazycommit Dec 25 '16 at 08:17
  • 3
    The question is about traits, which cannot implement interfaces. The above snippet will only work for classes. – Kafoso Apr 05 '17 at 13:13
  • 5
    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! – Armin Nov 29 '18 at 14:37
  • 2
    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. – J. D. Oct 16 '19 at 10:38
  • 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. – Kamafeather Jan 11 '21 at 23:41
  • Of course you may also choose to use abstract trait members in place of interfaces (see https://www.php.net/manual/en/language.oop5.traits.php#language.oop5.traits.abstract). – Lupinity Labs Mar 21 '21 at 14:26
  • This is the ideal solution. The Trait represents the default *yet optional* implementation of the very simple interface. – jchook Oct 03 '22 at 23:17
  • Downvoted. This doesn't answer OP's question, but rather provides an alternative (but good) solution. – S. Saad Oct 09 '22 at 20:19
29

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.

Rod Elias
  • 706
  • 6
  • 14
  • 3
    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 http://php.net/manual/en/function.class-uses.php – Jeremy Wadhams Dec 11 '18 at 05:13
  • 1
    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. – edigu Sep 25 '19 at 20:48
9

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')){
    ...
}
thesailorbreton
  • 363
  • 4
  • 12
7

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));
}
solarc
  • 5,638
  • 2
  • 40
  • 51
0

You can use Reflection as an additional option.

$reflection = new ReflectionClass($this)

$usedTraits = $reflection->getTraitNames();

if (in_array(MyTrait::class, $usedTraits)) {
    // do Something
}
jakub_jo
  • 1,494
  • 17
  • 22