【问题标题】:Return type "self" in abstract PHP class抽象 PHP 类中的返回类型“self”
【发布时间】:2026-02-05 00:10:01
【问题描述】:

PHP 7.1

我目前正在尝试创建一个抽象类来提供、定义和部分实现其子类的功能。

这里我使用以下构造:

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;

}

这里PHP似乎理解setName($name)返回一个类型为Parent的Object,但PhpStorm表示不能在结果上调用read(),这本来是预期的结果。

错误消息:在主题类中找不到引用的方法。

我不确定这是 PHP 还是 PhpStorm 中的错误,或者更有可能是我不明白自己在做什么......

我已阅读后期静态绑定和以下部分讨论此问题的问题,但我不知道如何解决它:

感谢您的时间和帮助。


编辑:如下所示,我正在尝试在子类中实现:

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

这显然不适用于 self return 但(IMO 应该)使用 static,这是被禁止的。

【问题讨论】:

    标签: php inheritance phpstorm late-static-binding


    【解决方案1】:

    尝试更改返回静态,静态应该表明您正在返回扩展类,这可能会消除错误。

    这是猜测工作,但值得一试。

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

    编辑:好的,我认为这不会,你能提供更多关于这个问题的信息吗?读书的目的是什么?来自数据库?等等?

    【讨论】:

    • 好吧,我觉得不行,你能提供更多关于这个问题的信息吗?读书的目的是什么?来自数据库?等等?
    • 仅供参考,尝试声明:static,因为您的返回类型会抛出E_FATAL
    • 许多 hanks 意识到这一点,因此希望获得有关 Simon 试图实现的目标的更多信息,以便我们重新设计。 @Simon 你能提供你想要达到的目标的细节吗,我认为这个类可以重构。
    • 好的,所以父类是 CMS 中一般对象的表示。它提供从数据库 (fromDB($pk))、数组或 json 的数据加载功能; setName 是标识值的设置器。 read 只是获取类的当前参数并尝试用数据库中的值填充它,因此它由 fromDB 调用;它还提供了一些魔术方法的抽象实现。
    • set name 需要是抽象方法吗?每个子类的实现是否不同?
    【解决方案2】:

    self始终引用其所在的类,从不子类。所以当Parent 说要返回:self 时,它期望你创建一个返回Parent 实例的函数。您需要做的是让您的子类声明它正在直接执行此操作。我们没有声明孩子的setName 将返回:self(这是Child 的一个实例),而是声明我们将直接返回我们的Parent 类。 PHP 认为这是完全有效的(毕竟ChildParent 的一个实例)

    我们不能在这里使用:static,因为static是类定义中的保留字

    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();
    

    以上代码runs without error

    【讨论】:

    • 部分修复了子类中的以下实现问题 - 请参阅有问题的编辑 - read() 的问题仍然存在
    • ok 奇怪的是,phpstorm 在尝试执行该代码时仍然显示错误并且其内部服务器崩溃,而 apache 则没有。跟进:我怎么还能从方法中得到一个孩子?
    • 我认为这可能是 PHPStorm 的一个错误(刚刚在 2017.1.4 上测试过,尽管有效,但它确实很适合)。这是一个边缘案例,我可以很容易地看到他们错过了它。
    • Child 声明可以有: parent 而不是: ParentTest,这样以后更容易重命名类。 (显然,Grandchild 不能使用类似的方法。)
    【解决方案3】:

    您的代码示例在 PhpStorm 2017.2(当前处于 EAP 阶段)中看起来不错,但在 2017.1.4 中显示警告。

    很可能它已被WI-33991 或相关票证之一修复。


    您可以随时从这里获取并试用 2017.2 EAP 版本:http://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Early+Access+Program

    它带有自己的 30 天许可证,可以与您当前的版本并行运行(IDE 范围的设置存储在单独的文件夹中)。

    【讨论】:

    • 我打开了一张票,它被关闭了,因为它是一个骗子,所以它在下一个版本中似乎是固定的
    【解决方案4】:

    恕我直言,有一种智能且可靠的方法来使用界面。 甚至应该由第三方使用的库也需要它。

    所以,我们只是在所有父类中将接口名称设置为类型而不是“self”,并继续为子类使用“self”。

    <?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();
    

    【讨论】: