PHP 8.0: Inheritance rules are not applied to private class methods

Version8.0
TypeChange

PHP class methods declared private are not accessible by child classes, but child classes can declare a method with the same name without having to adhere to Liskov Substitution Principle. PHP 8.0 strictly enforces method signatures, but private class methods are not affected by this change.

Typed Properties introduced in PHP 7.4 already follow the same changes, and is not changed in PHP 8.0.

Prior to PHP 8.0, it was not allowed change to change the final or static flags of a class method. This is changed in PHP 8.0, that ensures private methods can be truly private without adhering to the inheritance rules enforced from parent class(es).

In all PHP versions, private class method parameters, private method return types, and private class property types are not validated against Liskov Substitution Principle. These changes in PHP 8.0 only applies to changes in abstract, static, and final flags in class methods.

final private methods

Declaring a class method final private does not make sense because all private class methods are inaccessible by child classes anyway.

Prior to PHP 8.0, it was allowed to declare a class method as final private, but it was not allowed to redeclare the method due to final clause enforcement.

class Foo {
    final private function testFoo(): void {}
}

class ChildFoo extends Foo{
    private function testFoo(): void {}
}

This was not allowed prior to PHP 8.0, and resulted in a fatal error:

Fatal error: Cannot override final method Foo::testFoo() in ... on line ...

In PHP 8.0 and later, this restriction is removed, and only a warning is emitted:

Warning: Private methods cannot be final as they are never overridden by other classes in ... on line ...

Note that this warning is emitted when a class declares a method as final private; not when a child class re-declares a private method.

final private class constructors

An exception is made for the warning mentioned above for class constructors. It is possible to declare a class constructor as final private:

class Foo {
    final private function __construct() {}
}

Attempting to re-declare the constructor results in the same fatal error in all PHP versions.

class Foo {
    final private function __construct() {}
}

class ChildFoo extends Foo{
    final private function __construct() {}
}

// Fatal error: Cannot override final method Foo::__construct() in ... on line ...

static methods

Changing the static flag in an inheritance was not allow prior to PHP 8.0. It is allowed in PHP 8.0.

class Foo {
    private function bar() {}
    static private function baz() {}
}

class ChildFoo extends Foo{
    static private function bar() {} // Adds `static` flag from Foo
    private function baz() {} // Removes `static` flag from Foo
}

Prior to PHP 8.0, this caused fatal errors:

Fatal error: Cannot make non static method Foo::bar() static in class ChildFoo in ... on line ...
Fatal error: Cannot make static method Foo::baz() non static in class ChildFoo in ... on line ...

abstract methods

PHP does not allow to declare a private abstract function in any PHP version. In PHP 8.0 and later, it is allowed to declare a method as abstract even if a method with same name exists in the parent class.

class Foo {
    private function test() {}
}

abstract class ChildFoo extends Foo{
    abstract protected function test();
}

Prior to PHP 8.0, this resulted in a fatal error:

Fatal error: Cannot make non abstract method Foo::test() abstract in class ChildFoo in ... on line ...

Magic Methods

In PHP 8.0, magic method signatures are enforced. Magic methods (e.g __get, __set, __callStatic, etc) must follow the signature even if they are declared private.

Backwards Compatibility Impact

All private method signature patterns were not allowed and resulted in a fatal error in PHP versions prior to 8.0. As PHP 8.0 now allows it, these changes should not cause further errors in PHP 8.0

Declaring a class method final private emits a PHP warning at the declaration time of the parent class. This behavior is different from the fatal error that is only triggered if a child class attempted to extend/re-declare the same method.

Related Changes


RFC Discussion Implementation