Why Does PHP Allow "Incompatible" Constructors

Why does PHP allow incompatible constructors?

To understand why they are treated differently, you have to understand Liskov's Substitution Principle, which states

If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T." - BarbaraLiskov, Data Abstraction and Hierarchy, SIGPLAN Notices, 23,5 (May, 1988).

In a nutshell this means any class using your Lion or Cat should be able to reliably call doSomething on it, regardless of the class being one or the other. If you change the method signature, this is no longer guaranteed (you may widen it, but not narrow it though).

Very simple Example

public function doSomethingWithFeline(Cat $feline)
{
$feline->doSomething(42);
}

Since Lion extends Cat, you established an is-a relationship, meaning doSomethingWithFeline will accept a Lion for a Cat. Now imagine you add a required argument to doSomething in Lion. The code above would break because it is not passing that new param. Hence, the need for compatible signatures.

LSP does not apply to constructors though, because subtypes might have different dependencies. For instance if you have a FileLogger and a DBLogger, the ctors (constructors) of the first would require a filename, while the latter would require a db adapter. As such, ctors are about concrete implementations and not part of the contract between classes.

Why can't I overload constructors in PHP?

You can't overload ANY method in PHP. If you want to be able to instantiate a PHP object while passing several different combinations of parameters, use the factory pattern with a private constructor.

For example:

public MyClass {
private function __construct() {
...
}

public static function makeNewWithParameterA($paramA) {
$obj = new MyClass();
// other initialization
return $obj;
}

public static function makeNewWithParametersBandC($paramB, $paramC) {
$obj = new MyClass();
// other initialization
return $obj;
}
}

$myObject = MyClass::makeNewWithParameterA("foo");
$anotherObject = MyClass::makeNewWithParametersBandC("bar", 3);

When overloading a parent method, why did PHP8.1 change towards deprecating incompatible return types?

The change was necessary to bring consistency to the language. It makes for a very messy code if the overridden method can return a different type. It basically means that the overridden function does something entirely different. This behaviour was never allowed in PHP. Code like this would always throw:

class A {
public function foo():string {
return '';
}
}

class B extends A {
public function foo():int {
return 1;
}
}

The only problem was that the standard classes did not specify the return types internally. Many methods could not specify a type due to returning resources, mixed, union types, etc. This means that effectively they did not have a return type. PHP rules say that if the overridden method has no return type, the child method can specify (narrow) the type:

class A {
public function foo() { // this could also be :mixed but that was only introduced in PHP 8
return '';
}
}

class B extends A {
public function foo():int {
return 1;
}
}

So, you are asking the wrong question. The question isn't why the return type cannot be overridden since PHP 8.1, because that was always the case, but rather why PHP internal classes didn't specify the return type.

Since PHP 8.1 it became possible to declare most return types. However, due to the breaking change that this would cause, the internal methods only throw Deprecation message for the moment as compared to Fatal Error that would normally be produced. In PHP 9.0 all of this will be fixed.


For your particular case, you should be using composition rather than inheritance. Inheritance should be avoided most of the time, especially with internal classes. Composition offers more flexibility and is easier to test.

PHP Trait colliding constructor

you can resolve the constructor collision like this.

trait MyTrait {

use BaseSomeTrait{
BaseSomeTrait::__construct as private __otherConstruct;
}

public function __construct(/* maybe params here*/)
{
// maybe other code
$this->__otherConstruct(/* maybe params here*/);
// maybe other code
}
}

if MyClass has a constructor as well you need to do it there additionally, or maybe only there if MyTrait has no constructor...

Something wrong with Singleton class, no idea what

You need to write

$DBH = new static();

See: Late Static Binding



Related Topics



Leave a reply



Submit