为什么PHP 5.2+禁止抽象静态类方法?

Art*_*kii 120 php oop static abstract

在PHP 5.2中启用严格警告后,我看到一个项目的严格标准警告,这些警告最初是在没有严格警告的情况下编写的:

严格标准:静态函数 Program :: getSelectSQL()在Program.class.inc中不应该是抽象

有问题的函数属于抽象父类Program,并被声明为abstract static,因为它应该在其子类中实现,例如TVProgram.

我确实在这里找到了对此更改的引用:

删除了抽象的静态类函数.由于疏忽,PHP 5.0.x和5.1.x允许在类中使用抽象静态函数.从PHP 5.2.x开始,只有接口才能拥有它们.

我的问题是:有人可以清楚地解释为什么在PHP中不应该有一个抽象的静态函数?

Jon*_*and 76

静态方法属于声明它们的类.扩展类时,您可以创建一个同名的静态方法,但实际上并不是实现静态抽象方法.

使用静态方法扩展任何类都是一样的.如果扩展该类并创建相同签名的静态方法,则实际上并未覆盖超类的静态方法

编辑(2009年9月16日)
更新.运行PHP 5.3,我看到抽象静态回来了,无论好坏.(有关详细信息,请参阅http://php.net/lsb)


abstract static在PHP 5.3中仍然不允许CORRECTION(由philfreo),LSB是相关但不同的.

  • 在我看来,这个严格的警告只是愚蠢,因为PHP有​​"后期静态绑定",这自然提供了使用静态方法的想法,好像类本身就是一个对象(比如说,在ruby中).这导致静态方法重载和`abstract static`在这种情况下可能会有用. (39认同)
  • PHP 5.3中仍然不允许使用抽象静态.后期静态绑定与它无关.另见http://stackoverflow.com/questions/2859633 (7认同)
  • 这个答案含糊不清."尚不允许"只是意味着你会得到一个E_STRICT级别的警告,至少在5.3+我们很欢迎你来创建抽象静态功能,扩展类实现它们,然后通过静态::关键字引用它们.显然,父类的静态版本仍然存在,并且不能直接调用(通过self ::或::在该类中),因为它是抽象的,并且会像致命的非静态抽象函数一样致命错误.在功能上这是有用的,我同意@dmitry对这种效果的看法. (4认同)
  • 好的,那么如果我想在扩展我的抽象类的所有子项中强制需要函数getSelectSQL()呢?父类中的getSelectSQL()没有正当理由存在.什么是最好的行动计划?我选择抽象静态的原因是,在我在所有子节点中实现getSelectSQL()之前,代码不会编译. (3认同)

Mar*_*ery 75

这是一个漫长而悲伤的故事.

当PHP 5.2首次引入此警告时,后期静态绑定尚未使用该语言.如果您不熟悉后期静态绑定,请注意这样的代码不会像您期望的那样工作:

<?php

abstract class ParentClass {
    static function foo() {
        echo "I'm gonna do bar()";
        self::bar();
    }

    abstract static function bar();
}

class ChildClass extends ParentClass {
    static function bar() {
        echo "Hello, World!";
    }
}

ChildClass::foo();
Run Code Online (Sandbox Code Playgroud)

抛开严格模式警告,上面的代码不起作用.该self::bar()呼叫foo()明确提到bar()的方法ParentClass,即使在foo()被称为的方法ChildClass.如果您尝试在严格模式下运行此代码,您将看到" PHP致命错误:无法调用抽象方法ParentClass :: bar() ".

鉴于此,PHP 5.2中的抽象静态方法毫无用处.使用抽象方法的全部意义在于,您可以编写调用方法的代码,而无需知道它将调用哪个实现 - 然后在不同的子类上提供不同的实现.但是,由于PHP 5.2没有提供编写父类的方法的简洁方法,该方法调用调用它的子类的静态方法,因此无法使用抽象静态方法.因此abstract static,PHP 5.2中的任何使用都是错误的代码,可能是由于对self关键字如何工作的误解所致.对此发出警告是完全合理的.

但是随后PHP 5.3增加了引用通过static关键字调用方法的类的能力(与self关键字不同,关键字总是指定义方法的类).如果您更改self::bar()static::bar()在我上面的例子,它工作在PHP 5.3及以上的罚款.你可以在New self和new static上阅读更多关于selfvs的内容.static

添加静态关键字后,abstract static抛出警告的明确参数消失了.后期静态绑定的主要目的是允许父类中定义的方法调用将在子类中定义的静态方法; 鉴于存在后期静态绑定,允许抽象静态方法似乎是合理且一致的.

我想,你仍然可以提出保持警告的理由.例如,您可以争辩说,因为PHP允许您调用抽象类的静态方法,在上面的示例中(即使在通过替换self来修复它之后static),您暴露了一个ParentClass::foo()破坏的公共方法,并且您并不真正想要暴露.使用非静态类 - 即,使所有方法实例方法并使所有方法ParentClass成为单例或其他东西 - 将解决此问题,因为ParentClass,是抽象的,无法实例化,因此其实例方法不能叫做.我认为这个论点很弱(因为我觉得暴露ParentClass::foo()并不是什么大问题,使用单身而不是静态类通常是不必要的冗长和丑陋),但你可能会合理地反对 - 这是一个有点主观的呼吁.

所以基于这个论点,PHP开发人员用语言保留了警告,对吧?

呃,不完全是.

上面链接的PHP错误报告53081要求删除警告,因为添加static::foo()构造使得抽象静态方法合理且有用.Rasmus Lerdorf(PHP的创建者)开始时将请求标记为虚假,并经历了一系列糟糕的推理,试图证明警告的合理性.然后,最后,这次交换发生:

乔治

我知道但是:

abstract class cA
{
      //static function A(){self::B();} error, undefined method
      static function A(){static::B();} // good
      abstract static function B();
}

class cB extends cA
{
    static function B(){echo "ok";}
}

cB::A();
Run Code Online (Sandbox Code Playgroud)

拉斯穆斯

是的,这正是它应该如何运作的.

乔治

但是不允许:(

拉斯穆斯

有什么不允许的?

abstract class cA {
      static function A(){static::B();}
      abstract static function B();
}

class cB extends cA {
    static function B(){echo "ok";}
}

cB::A();
Run Code Online (Sandbox Code Playgroud)

这很好用.你显然不能调用self :: B(),但是static :: B()很好.

Rasmus声称他的例子中的代码"工作正常"是错误的; 如你所知,它会抛出严格模式警告.我猜他没有打开严格模式就开始测试了.无论如何,一个混乱的Rasmus将请求错误地关闭为"虚假".

这就是警告仍在语言中的原因.这可能不是一个完全令人满意的解释 - 你可能来到这里希望警告有合理的理由.不幸的是,在现实世界中,有时选择是出于世俗的错误和错误的推理而不是理性的决策.这只是其中一次.

幸运的是,可评估的Nikita Popov已经删除了PHP 7中语言的警告,作为PHP RFC的一部分:重新分类E_STRICT通知.最终,理智得到了普及,一旦PHP 7发布,我们都可以愉快地使用abstract static而不会收到这个愚蠢的警告.


Wou*_*iet 70

对于这个问题,有一个非常简单的方法,从设计的角度来看,这实际上是有意义的.正如乔纳森所写:

使用静态方法扩展任何类都是一样的.如果扩展该类并创建相同签名的静态方法,则实际上并未覆盖超类的静态方法

所以,作为一个解决方法你可以做到这一点:

<?php
abstract class MyFoo implements iMyFoo {

    public static final function factory($type, $someData) {
        // don't forget checking and do whatever else you would
        // like to do inside a factory method
        $class = get_called_class()."_".$type;
        $inst = $class::getInstance($someData);
        return $inst;
    }
}


interface iMyFoo {
    static function factory($type, $someData);
    static function getInstance();
    function getSomeData();
}
?>
Run Code Online (Sandbox Code Playgroud)

现在,您强制执行任何子类化MyFoo的类实现getInstance静态方法和公共getSomeData方法.如果你不是MyFoo的子类,你仍然可以实现iMyFoo来创建一个具有类似功能的类.

  • 有时候`static ::`会很有用. (3认同)
  • 不适用于Traits.如果只有Traits可以有`abstract static`方法,没有PHP婊子.... (3认同)
  • 是否可以使用此模式使功能**受保护**.当我这样做时,它意味着扩展MyFoo的类会抛出getInstance必须公开的警告.并且您不能将protected放在接口定义中. (2认同)

Pet*_*tah 12

我知道这已经过时了......

为什么不抛出那个父类的静态方法的异常,这样如果你不覆盖它就会导致异常.

  • @BT我的意思是,不要声明方法是抽象的,实现它,而只是在调用它时抛出异常,这意味着如果它被覆盖它就不会抛出. (3认同)