Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

phpstan: how to handle derived class return types

Tags:

php

phpstan

What's the right way to fix the error generated by phpstan for this sample code? The error message is:

Method Foo::foo() should return Child but returns Base.

<?php declare(strict_types = 1);

Interface MyI {
    abstract function a(): self;
}
Class Base implements MyI { 
    public function a(): self {return $this;} 
}
Class Child extends Base { 
    public function c(): self {return $this;} 
}
Class Foo {
    public function factory(): Child {
        return new Child();
    }
    /**
     * @return Child
     */
    public function foo() /* note no return type */  {
        return $this->factory()
            ->c()
            ->a();
    }
}

One way to eliminate the error is to change the @return to read:

/**
 * @return Child | Base
 */

But I greatly dislike that because foo() never returns an instance of Base. The code only ever works with the derived class Child. In fact, the interface and base classes are both in a library (dependency), so I mostly cannot even change that code.

I could re-implement a() in my Child class like this:

    public function a(): Child {
        parent::a();
        return $this;
    }

But that is also ugly, and worse, this is a toy example. In the real class, I'd have to re-implement many methods. Very bad.

like image 707
CXJ Avatar asked Sep 02 '25 04:09

CXJ


2 Answers

You need @return static for that: https://phpstan.org/r/42ee409c-ada2-44c9-a26d-6a85c2327bca

To learn about all different PHPDoc posibilities head to PHPStan documentation:

  • https://phpstan.org/writing-php-code/phpdocs-basics
  • https://phpstan.org/writing-php-code/phpdoc-types
like image 136
Ondřej Mirtes Avatar answered Sep 05 '25 00:09

Ondřej Mirtes


Had something similar too, although my case was a bit different.

My code was:

trait X
{
    protected function createFoo(string $childClassName): BaseClass
    {
        // do something...

        return $childClassName::createFrom($this);
    }
}

And I was getting the same error as OP:

public function bar(): ChildClass
{
    return $this->createFoo(ChildClass::class);
        // phpstan error: Method bar() should return Child but returns Base
}

After reading briefly about phpstan generics, ended up with the following phpdoc. Seems to solve the issue the right way:

trait X
{
    /**
     * @template T of BaseClass
     * @param class-string<T> $childClassName
     * @return T
     */
    protected function createFoo(string $childClassName): BaseClass
    {
        // do something...

        return $childClassName::createFrom($this);
    }
}

Hope this helps.

like image 20
volvpavl Avatar answered Sep 05 '25 00:09

volvpavl