为什么PHP 5.2+不允许抽象的静态类方法?

在PHP 5.2中启用严格的警告之后,我看到一个严格标准警告来自一个原本没有严格警告的编写的警告:

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

有问题的函数属于一个抽象的父类程序,并被声明为抽象静态的,因为它应该在其子类(如TVProgram)中实现。

我确实在这里find了这个变化的参考:

丢弃抽象的静态类function。 由于疏忽,PHP 5.0.x和5.1.x允许在类中使用抽象静态函数。 从PHP 5.2.x开始,只有接口可以拥有它们。

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

静态方法属于声明它们的类。 扩展类时,可以创build一个名称相同的静态方法,但实际上并没有实现静态的抽象方法。

用静态方法扩展任何类也一样。 如果扩展该类并创build相同签名的静态方法,则实际上并不会覆盖超类的静态方法

编辑 (2009年9月16日)
更新这个。 运行PHP 5.3,我看到抽象的静态回来了,好或坏。 (更多信息请参见http://php.net/lsb

更正 (由philfreo)
PHP 5.3中仍然不允许abstract static , LSB是相关但不同的。

这个问题有一个非常简单的解决方法,从devise的angular度来看,这实际上是有意义的。 正如乔纳森所写:

用静态方法扩展任何类也一样。 如果扩展该类并创build相同签名的静态方法,则实际上并不会覆盖超类的静态方法

所以,作为一个工作可以做到这一点:

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

而现在你强制任何类inheritanceMyFoo实现了一个getInstance静态方法和一个公共getSomeData方法。 如果你没有inheritanceMyFoo,你仍然可以实现iMyFoo创build一个具有类似function的类。

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

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

抛开严格的模式警告,上面的代码不起作用。 foo()self::bar()调用明确指向ParentClassbar()方法,即使在调用foo()作为ChildClass的方法时也是如此。 如果您尝试以严格模式运行此代码,您将看到“ PHP致命错误:不能调用抽象方法ParentClass :: bar() ”。

鉴于此,PHP 5.2中的抽象静态方法是无用的。 使用抽象方法的全部意义在于,您可以编写调用该方法的代码,而不必知道它将调用哪个实现,然后在不同的子类上提供不同的实现。 但是由于PHP 5.2没有提供干净的方法来编写一个父类的方法,该方法调用它所在的子类的静态方法,所以这种抽象静态方法的使用是不可能的。 因此,在PHP 5.2中对abstract static任何使用都是错误的代码,可能是由于对self关键字的工作方式的误解而引起的。 对此提出警告是完全合理的。

但是随后PHP 5.3增加了引用通过static关键字调用方法的类的能力(不像self关键字,它总是指定义方法的类)。 如果在上面的例子中将self::bar()更改为static::bar() ,则在PHP 5.3及更高版本中可以正常工作。 你可以阅读更多关于selfstatic在新的自我与新的静态 。

随着静态关键字的添加, abstract static抛出警告的明确参数消失了。 晚期静态绑定的主要目的是允许在父类中定义的方法调用将在子类中定义的静态方法; 允许抽象的静态方法看起来是合理和一致的,因为存在延迟的静态绑定。

我猜,你仍然可以保留警告。 例如,你可以争辩说,因为PHP允许你调用抽象类的静态方法,在我上面的例子中(即使通过用staticreplaceself修复它),你正在公开一个公共方法ParentClass::foo()你真的不想公开 使用一个非静态类 – 也就是说,使所有的方法实例方法,使所有的ParentClass的孩子都是单身或什么东西 – 将解决这个问题,因为ParentClass ,是抽象的,不能实例化,所以它的实例方法可以不叫。 我认为这个说法是微不足道的(因为我认为暴露ParentClass::foo()不是什么大问题,而使用单例而不是静态类通常是不必要的冗长和丑陋的),但是你可能有理由不同意 – 这是一个有些主观的意见。

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

呃, 不完全是 。

上面链接的PHP错误报告53081要求在添加static::foo()构造时使警告被丢弃,这使得抽象的静态方法变得合理和有用。 Rasmus Lerdorf(PHP的创build者)首先将请求标记为假,并经历一连串不良推理的尝试来certificate警告。 最后,这个交换发生了:

乔治

我知道但是:

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

拉斯穆斯

对,这正是它应该如何工作。

乔治

但不允许:(

拉斯穆斯

什么是不允许的?

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

这工作正常。 你显然不能调用self :: B(),但是static :: B()很好。

Rasmus声称他的例子中的代码“正常工作”是错误的; 如你所知,它会引发严格的模式警告。 我猜他没有打开严格的模式进行testing。 无论如何,一个混淆的Rasmus将请求错误地closures为“假”。

这就是为什么警告还在语言中。 这可能不是一个完全令人满意的解释 – 你可能来到了这里,希望有一个理性的警告理由。 不幸的是,在现实世界中,有时select是由世俗的错误和错误的推理而产生的,而不是理性的决策。 这只是其中的一个。

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

我知道这是旧的,但….

为什么不只是抛出一个exception的父类的静态方法,这样,如果你不覆盖它的exception是造成的。

我认为抽象类/接口可以被看作程序员之间的契约。 它更多地处理事物的外观/行为,而不是实际的function。 正如在php5.0和5.1.x中看到的,防止php开发人员这样做并不是一个自然规律,而是希望与其他语言的其他OOdevise模式一起使用。 基本上这些想法试图防止意想不到的行为,如果一个已经熟悉其他语言。

我没有看到任何理由禁止静态抽象函数。 没有理由禁止他们的最好的说法是,他们被允许在Java中。 问题是: – 技术上可行吗? – 是的,因为存在于PHP 5.2中,并且存在于Java中。 所以,当可以做到这一点。 我们应该这样做吗? – 他们有道理吗? 是。 实现一个类的一部分并把一个类的另一部分留给用户是有意义的。 这对于非静态函数是有意义的,为什么静态函数没有意义呢? 静态函数的一个用途是不能有多于一个实例(单例)的类。 例如一个encryption引擎。 它不需要在几个实例中存在,并且有理由来防止这种情况 – 例如,您必须只保护内存的一部分免受入侵者的侵害。 因此,实现引擎的一部分并将encryptionalgorithm留给用户是非常有意义的。 这只是一个例子。 如果你习惯使用静态函数,你会发现更多。

在PHP 5.4+使用特质:

 trait StaticExample { public static function instance () { return new self; } } 

并在你的class级开始酝酿:

 use StaticExample; 

看看PHP的“后期静态绑定”问题。 如果你将静态方法放在抽象类上,那么你可能会更快地遇到它,而不是晚些时候。 严格的警告告诉你避免使用破碎的语言特征是有道理的。