Return type "self" in abstract PHP class
Asked Answered
C

4

15

Trying to make an abstract class to partially implement functionality of its' child classes and force a contract upon them required for this implementation, I use the following construct:

abstract class Parent {

    public static function fromDB(string $name = '') {
        $instance = new static();
        if (!empty($name)) {
            $instance->setName($name)->read();
        }
        return $instance;
    }

    public abstract function read();

    public abstract function setName(string $name): self;

}

Here PHP seems to understand that setName($name) returns an Object of type Parent, but PhpStorm indicates that read() can not be called on the result, which would have been the expected result.

Error Message: Referenced Method is not found in subject class.

I do not understand why this happens, and even suspect a bug in PHP or PhpStorm.

I've read up on Late static binding and the following questions which partially talk about this problem, but I couldn't figure out how to fix it:

Thank you for your time and help.

I'm trying to implement setName in child classes in a way so it is clear that the type of the returned Object is the one of the child:

public function setName(string $name = null): user {...}

which doesn't work with self return type and static is forbidden.

Contrariety answered 28/6, 2017 at 13:4 Comment(0)
W
5

Your code sample looks fine in PhpStorm 2017.2 (currently in EAP stage) but shows warning in 2017.1.4.

enter image description here

Quite possibly it was fixed by WI-33991 or one of the related tickets.


You may get & try the 2017.2 EAP build at any time from here: http://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Early+Access+Program

It comes with own 30-days license and can run in parallel to your current version (IDE-wide settings are stored in separate folders).

Wrung answered 28/6, 2017 at 15:27 Comment(1)
I opened a ticket and it was closed as a dupe of that one, so it does appear fixed in the next versionLignin
L
7

self will always refer to the class its in, never children classes. So when Parent says to return :self, it's expecting you to create a function that returns an instance of Parent. What you need to do is have your child class declare that it's doing that directly. Instead of declaring the child's setName will return :self (which is an instance of Child), we declare we're going to return our Parent class directly. PHP sees this as perfectly valid (Child is an instance of Parent after all)

We cannot use :static here because static is a reserved word in the class definitions

abstract class ParentTest {

    public static function fromDB(string $name = '') {
        $instance = new static();
        if (!empty($name)) {
            $instance->setName($name)->read();
        }
        return $instance;
    }

    public abstract function read();

    public abstract function setName(string $name): self;

}

class Child extends ParentTest {
    public function read() {

    }

    public function setName(string $name) :ParentTest {
        return $this;
    }
}

$child = new Child();

The above code runs without error

Lignin answered 28/6, 2017 at 13:13 Comment(4)
fixes the following problem of implementation in child class partially - see edit in question - the problem with read() persistsContrariety
ok wierdly enough phpstorm still shows the error and its internal server crashes when trying to execute that code, while apache doesn't. Follow up: how can i still get a child from the method?Contrariety
I'm thinking that might be a bug with PHPStorm then (just tested on 2017.1.4 and it does throw a fit despite being valid). This is an edge case and I could easily see them having missed it.Lignin
The Child declaration can have : parent rather then : ParentTest which would make it easier to rename the class later. (Obviously a similar approach can't be used for a Grandchild.)Collectivize
W
5

Your code sample looks fine in PhpStorm 2017.2 (currently in EAP stage) but shows warning in 2017.1.4.

enter image description here

Quite possibly it was fixed by WI-33991 or one of the related tickets.


You may get & try the 2017.2 EAP build at any time from here: http://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Early+Access+Program

It comes with own 30-days license and can run in parallel to your current version (IDE-wide settings are stored in separate folders).

Wrung answered 28/6, 2017 at 15:27 Comment(1)
I opened a ticket and it was closed as a dupe of that one, so it does appear fixed in the next versionLignin
P
2

IMHO, there is an intelligent and SOLID approach to use interfaces. It is even required for libraries supposed to be used by 3rd-parties.

So, we just set the interface name as a type instead of 'self' in all parent classes and continue using 'self' for children.

<?php
interface EntityInterface 
{
    public function setName(string $name): EntityInterface;
}

abstract class ParentTest implements EntityInterface 
{

    public abstract function read();

    public abstract function setName(string $name): EntityInterface;

}

class Child extends ParentTest 
{
    
    private string $name = '';
    
    public function read(): string 
    {
        return $this->name;
    }

    public function setName(string $name): self 
    {
        $this->name = $name;
        return $this;
    }
}

$child = new Child();
echo $child->setName('hello')->read();
Primer answered 25/11, 2020 at 11:56 Comment(0)
E
-2

Try change to return static, static should indiciating you are returning the extending class, which may remove the error.

This is guess work but it's worth a shot.

public abstract function setName(string $name): static;

EDIT: Ok i dont think that will do, can you provide more information on the question? What is the purpose of read? fromDb? etc?

Explicate answered 28/6, 2017 at 13:7 Comment(7)
Ok i dont think that will do, can you provide more information on the question? What is the purpose of read? fromDb? etc?Explicate
Just FYI, trying to declare :static as your return type throws a E_FATALLignin
Manyt hanks, realised this so hoping for more info on what Simon is trying to achieve so we can look at redesign. @Contrariety can you provide details of what you are trying to achieve, i think this class can be refactored.Explicate
ok so the Parent Class is a representation of a general object in a CMS. It provides dataloading capabilities from Database (fromDB($pk)) , from arrays or from json; setName is baysicalls the setter for the identifying value. read just takes the classes current parameters and tries to fill it with values from database, hence its call by fromDB; It also provides abstract implementations for a few magic methods.Contrariety
Does set name need to be an abstract method? Does its implementation differ per subclass?Explicate
yes, sadly yes one class represents a user which holds some limitations, since there is a login mechanismContrariety
to be more precise - its implementation accesses a field named "name" which not all classes have...Contrariety

© 2022 - 2024 — McMap. All rights reserved.