Search code examples
phpoopdeprecation-warninglanguage-conceptsphp-8.1

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


I overloaded query method of mysqli class like so:

class MySql extends \mysqli
{
    function query(string $sql): ?MySqlResult  // line #30
    {
        $result = parent::query($sql);
        return new MySqlResult($result);
    }
}

in PHP8.0 that was not an issue. However, as of PHP8.1 I am now getting this error:

Deprecated: Return type of Repository\MySql\MySql::query($sql, $resultmode = null) should either be compatible with mysqli::query(string $query, int $result_mode = MYSQLI_STORE_RESULT): mysqli_result|bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in repository\src\MySql\MySql.php on line 30

I know how to fix the error - I will probably end up changing the name of the method, since I want to return a my own custom object.

Question

I am looking for an answer that captures the need for this change from a theoretical and object-oriented perspective, maybe using language theory, or comparing it to other languages.

Why was this change necessary? What was the need or what was the reason to make this change? What there a way to allow overloaded return types in PHP when extending a class?


Solution

  • 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 built-in 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'S built-in 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 methods of built-in classes 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 built-in classes. Composition offers more flexibility and is easier to test.