BUG: Constructors, Interfaces, and Abstracts Don’t Mix Well

I just discovered a bug today in PHP 5.1 (haven’t confirmed if it was fixed in newer versions). When trying to enforce interface arguments on constructors, PHP behaves unexpectedly. Normally, interfaces allow you to enforce argument counts or types in child class methods, but not with the constructor (and probably destructor).

Crash course on interfaces: An interface lets you as a developer dictate a standard for a class. For example, you might write an interface class for interacting with your class. Then other people who want to interact with your class would “implement” your interface class. This would force their classes to have a certain set of methods, of which you dictate their names and argument counts (and types). This way, your class is always guaranteed these implementer classes have certain key methods. In the real life example, it’s like saying an interface for a Car would have methods like brake($amount), gas($amount), steer($direction), etc, and the User class would be able to have a guaranteed way of interacting with the Car object (i.e., $user->getCar(‘Ferrari’)->steer(‘left’)). Abstract methods exist in abstract classes and are essentially the same thing. Read more about these here and here.

First, here is an example of a typical interface:

class ExampleClass {}

interface TestInterface {
	public function output(ExampleClass $var);
}

class Test implements TestInterface {
	// error, no output() method was defined
}

The following fails too:

class ExampleClass {}

interface TestInterface {
	public function output(ExampleClass $var);
}

class Test implements TestInterface {
	public function output($var) {} // error, wrong argument type
}

Here is the same example but with the __construct method instead:

class ExampleClass {}

interface TestInterface {
	public function __construct(ExampleClass $var);
}

class Test implements TestInterface {
	// error, no __construct() method was defined
}

Up to here, it works as expected. However, if you define the constructor, the __construct method argument datatype/count checks go out the window:

class ExampleClass {}

interface TestInterface {
	public function __construct(ExampleClass $var);
}

class Test implements TestInterface {
	public function __construct() {} // NO ERROR
}

Despite the data types and argument count being off, PHP doesn’t care. Even if I define an argument in the constructor, the datatype check is ignored. So the best you can do is force a __construct() definition to be required, but you can’t dictate its arguments (i.e., interfaces for constructor methods are useless). And finally, for those of you really astute readers:

class ExampleClass {}

abstract class AbstractTest {
	abstract public function __construct(ExampleClass $var);
}

class Test extends AbstractTest {
	public function __construct() {} // NO ERROR
}

This problem produces the SAME results if instead of an interface, abstract methods in an abstract parent class are used.

  • http://www.michikono.com Michi

    This actually has been fixed in PHP 5.25. It was not a feature; it was a bug.

    The point of interfaces is to define how you interact with a class — if a class can have a different constructor despite an interface telling it otherwise, the entire paradigm breaks down, no?

    What if I wanted to make an interface for a factory pattern class? In such a case, being able to tell implementers that the constructor MUST be protected makes a lot of sense. Why in the world is taking that a step further and enforcing a certain type of argument not make sense? What if my factory interface was for classes that convert other classes, and thus expect a certain class object as a constructor argument?

  • http://www.stderr.nl Matthijs Kooijman

    It might be me, but it seems this behaviour is really intentional. I’ve never seen another OO language that allows constructors in an interface, let alone enforces implementing classes to have those. An interface is mainly meant to be able to talk to an object that’s already instantiated, even when you don’t really know it’s type (just that it conforms to some interface). Due to the lack of type-safety in PHP, this is a bit confusing (it reduces interfaces mainly to extra checks for the compiler…)

    As for the abstract parent class, it’s actually a feature that the constructor arguments of the child class can be different. It’s not uncommon for a parent class to have some constructor argument that will be different depending on the subclass used, not depending on how you call the constructor.

  • http://www.samuofm.com Sam Jones

    Thanks Michi!