Josef Sábl
Josef Sábl

Reputation: 7752

Declaration of Specific::method(): must be compatible with General::method(). Is PHP wrong about LSP?

I guess that the "... must be compatible with ..." is in place to enforce Liskov Substitution Principle. But I am not sure this is what LSP says?

I have a code like this:

class General
{
    public static function create(): General
    {
        return new static;
    }

    public function doSomething()
    {
        echo get_class($this) . ' speaking!' . PHP_EOL;
    }
}


class Specific extends General
{
    public static function create(): Specific
    {
        return parent::create();
    }
}


function doSomething(General $object)
{
    $object->doSomething();
}

doSomething(General::create());
doSomething(Specific::create());

Which produces:

PHP Fatal error: Declaration of Specific::create(): Specific must be compatible with General::create(): General in ...

The LSP is often cited as:

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

And this is not violated here as far as I understand. So what is wrong here? Is it some special restriction that doesn't have anything to do with LSP? Is it a bug in PHP? Am I doing something wrong without knowing?

UPDATE: I found this thread (Parameter type covariance in specializations). I understand and fully agree that the example there violates LSP. But my situation is different (reverse in fact).

Upvotes: 3

Views: 1190

Answers (2)

whyer
whyer

Reputation: 918

The LSP states that method parameters must be contravariant while return values must be covariant.

In your case you have covariant return types which satisfies the LSP.

The problem is in PHP itself. This restriction has nothing to do with LSP, it's just that earlier versions of PHP haven't implemented that yet.

Since PHP 7.2 (7.4) it now supports fully the LSP, i.e. parameter contravariance and return value covariance: https://www.php.net/manual/en/language.oop5.variance.php

UPD. But your code contains another issue: your \Specific::create() method must return an instance of Specific as you have stated it in its signature, but it tries to return the value returned by \General::create() which is stated to be General (and we know that instances of General are not instanceof Specific). This is quite misleading. E.g. PhpStorm will warn you about it: Return value is expected to be 'Specific', 'General' returned. But PHP doesn't throw an error about that.

This could be fixed by adding a PhpDoc to the \General::create():

class General
{
    /**
     * @return static
     */
    public static function create(): General
    {
        return new static;
    }

Upvotes: 1

ceejayoz
ceejayoz

Reputation: 180126

http://php.net/manual/en/functions.returning-values.php

When overriding a parent method, the child's method must match any return type declaration on the parent. If the parent doesn't define a return type, then the child method may do so.

Your Specific::create function should indicate a General return type.

Otherwise, code written for Specific::create would potentially break when running General::create, as it would receive a different class.

Upvotes: 1

Related Questions