January 19, 2011

How to detect if a class implements an Interface?

I'm in the real need to know if, given a string className or object, it implements a particular interface. I'm specially interested in the string className part.

During the search, many seemingly valid options appeared: instanceof, is_subclass_of, is_a, ReflectionClass::ImplementsInterface, class_implements… Which one to use? Let's do some testing!

The test

I coded the following test to determine the behavior of different options:

interface MyInterface { }

class NotInterfaced { }

class Interfaced implements MyInterface { }

class InterfacedDerived extends Interfaced { }

function dotest($functionName, $result)
{
    echo ($result ? '☑' : '☒'), ' ', $functionName, PHP_EOL;
}

function test($object, array $functions)
{
    $className = get_class($object);
    echo "{$className} implements MyInterface?", PHP_EOL;
    foreach ($functions as $functionName => $function) {
        dotest("string {$functionName}", $function($className));
        dotest("object {$functionName}", $function($object));
    }
    echo PHP_EOL;
}

$functions = array(
    'instanceof' => function($type) {
        return $type instanceof MyInterface;
    },
    'is_a' => function($type) {
        return is_a($type, 'MyInterface');
    },
    'is_subclass_of' => function($type) {
        return is_subclass_of($type, 'MyInterface');
    },
    'class_implements' => function($type) {
        return in_array('MyInterface', class_implements($type));
    },
    'ReflectionClass::ImplementsInterface' => function($type) {
        $rc = new ReflectionClass($type);
        return $rc->implementsInterface('MyInterface');
    },
);

test(new NotInterfaced,     $functions);
test(new Interfaced,        $functions);
test(new InterfacedDerived, $functions);

This checks every option with three different classes, using both the classname and a real instance of the class. And now, the results:

NotInterfaced

NotInterfaced implements MyInterface?
☒ string instanceof
☒ object instanceof
☒ string is_a
☒ object is_a
☒ string is_subclass_of
☒ object is_subclass_of
☒ string class_implements
☒ object class_implements
☒ string ReflectionClass::ImplementsInterface
☒ object ReflectionClass::ImplementsInterface

NotInterfaced is clearly an edge case to check if the tests are doing fine. The results are all correct, since the class doesn't implement the MyInterface interface.

Interfaced

Interfaced implements MyInterface?
☒ string instanceof
☑ object instanceof
☒ string is_a
☑ object is_a
☒ string is_subclass_of
☒ object is_subclass_of
☑ string class_implements
☑ object class_implements
☑ string ReflectionClass::ImplementsInterface
☑ object ReflectionClass::ImplementsInterface

Here we can extract some info. All methods worked well with objects, except for is_subclass_of. Since it neither works with strings, at this point, I thought is_subclass_of was not a valid option at all.

The other bit of info is that only class_implements and ReflectionClass::ImplementsInterface worked well with both string classNames and objects. Fine, as long as they're as fast as any of the other options.

InterfacedDerived

InterfacedDerived implements MyInterface?
☒ string instanceof
☑ object instanceof
☒ string is_a
☑ object is_a
☑ string is_subclass_of
☑ object is_subclass_of
☑ string class_implements
☑ object class_implements
☑ string ReflectionClass::ImplementsInterface
☑ object ReflectionClass::ImplementsInterface

Some more food for thought… And a surprise. Now, suddenly, is_subclass_of worked well, not only for object parameters, but for string classNames too. A further check to it's name reveals the answer: InterfacedDerived is a subclass of Interfaced which, in turn, implements MyInterface. So we can say the behavior was correct. My intuition, wasn't.

As with previous test, instanceof and is_a give good results for checking objects, as good as class_implements and ReflectionClass::ImplementsInterface for string classNames do.

In Conclusion

To check if an object implements a given interface, you can choose between:

  • instanceof
  • is_a
  • class_implements
  • ReflectionClass::ImplementsInterface

To check if a class implements a given interface, given only its class name, you should choose between:

  • class_implements
  • ReflectionClass::ImplementsInterface

Which are your choices? Know of any other way to do it?

No comments:

Post a Comment