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


121

在PHP 5.2中启用严格警告之后,我看到了一个项目中的大量严格标准警告,这些项目最初编写时没有严格警告:

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

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

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

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

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


12
新的读者应该注意到,这种不合理的限制已经在PHP 7.被删除
马克·阿梅里奥

Answers:


76

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

使用静态方法扩展任何类也是如此。如果扩展该类并创建具有相同签名的静态方法,则实际上并没有覆盖超类的静态方法。

编辑(2009年9月16日)
对此进行了更新。运行PHP 5.3,我看到抽象静态技术又回来了,不管是好是坏。(有关更多信息,请参见http://php.net/lsb


abstract staticPHP 5.3仍然不允许更正(由philfreo进行),LSB是相关的但有所不同。


3
好的,如果我想在扩展我的抽象类的所有子类中强制执行功能getSelectSQL()怎么办?父类中的getSelectSQL()没有有效的理由存在。最好的行动计划是什么?我选择抽象静态的原因是,直到我在所有子代中都实现了getSelectSQL()之后,代码才能编译。
Artem Russakovskii 09年

1
最有可能的是,您应该重新设计事物,以便getSelectSQL()是抽象的/ instance /方法。这样,每个孩子的/ instances /都将具有这种方法。
马修·弗拉申(Malays Flaschen)2009年

7
PHP 5.3中仍然不允许使用抽象静态函数。后期静态绑定与它无关。另请参见stackoverflow.com/questions/2859633
Artefacto 2010年

40
在我看来,这种严格的警告只是愚蠢的,因为PHP具有“后期静态绑定”,这自然提供了使用静态方法的想法,就好像类本身是对象一样(例如在ruby中)。这导致静态方法重载,abstract static在这种情况下可能很有用。
德米特里

4
这个答案显然是错误的。“仍然不允许”只是意味着您将收到E_STRICT级别的警告,至少在5.3+中,完全欢迎您创建抽象的静态函数,在扩展类中实现它们,然后通过static ::关键字引用它们。显然,父类的静态版本仍然存在,不能直接调用(通过该类内部的self ::或static::),因为它是抽象的,并且会致命错误,就像您调用常规的非静态抽象函数一样。从功能上来说,这很有用,我同意@dmitry的观点。
ahoffner 2014年

79

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

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

撇开严格模式警告,上面的代码不起作用。中的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 vs. new static上了解有关selfvs的更多信息。static

添加了static关键字后,用于abstract static发出警告的clear参数就消失了。后期静态绑定的主要目的是允许在父类中定义的方法调用将在子类中定义的静态方法。考虑到后期静态绑定的存在,允许抽象静态方法似乎是合理且一致的。

我想,您仍然可以为保持警告辩护。例如,您可能会争辩说,由于PHP允许您调用抽象类的静态方法,因此在上面的示例中(即使通过用替换self了它,也可以修复static),您正在公开一个损坏的公共方法ParentClass::foo(),并且您真的不想暴露。使用非静态类(即使所有方法的实例方法并使所有子方法成为单例或诸如此类)将解决此问题,因为不能被抽象化,因此其实例方法不能被实例化。叫做。我认为这个论点很弱(因为我认为公开ParentClassParentClassParentClass::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();

拉斯穆斯

是的,这就是它应该如何工作的。

乔治

但这是不允许的:(

拉斯穆斯

不允许什么?

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声称他的示例中的代码“可以正常工作”是错误的;如您所知,它会发出严格的模式警告。我猜他在没有打开严格模式的情况下进行测试。无论如何,一个困惑的拉斯穆斯将请求错误地关闭为“伪造”。

这就是为什么警告仍然使用该语言。这可能不是一个完全令人满意的解释-您可能来到这里是希望警告的合理理由。不幸的是,在现实世界中,有时候选择是来自平凡的错误和错误的推理,而不是理性的决策。这只是其中一次。

幸运的是,作为PHP RFC的一部分,可估算的Nikita Popov已从PHP 7语言中删除了该警告:重新分类E_STRICT声明。最终,理智性盛行,一旦PHP 7发布,我们所有人都可以愉快地使用abstract static而不会收到这个愚蠢的警告。


70

对于此问题有一个非常简单的解决方法,从设计的角度来看,这实际上是有意义的。正如乔纳森(Jonathan)写道:

使用静态方法扩展任何类也是如此。如果扩展该类并创建具有相同签名的静态方法,则实际上并没有覆盖超类的静态方法。

因此,作为一种解决方法,您可以执行以下操作:

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

现在,您可以强制所有子类MyFoo的子类都实现getInstance静态方法和公共getSomeData方法。而且,如果您不继承MyFoo的子类,则仍然可以实现iMyFoo来创建具有类似功能的类。


2
这种模式是否有可能使功能受到保护。当我这样做时,它意味着扩展MyFoo的类将发出警告,指出getInstance必须是公共的。而且您不能在接口定义中加入保护。
artfulrobot

3
有时static::会有用。
2014年

3
与特质不兼容。如果只有性状可以有abstract static方法,没有PHP婊子....
Rudie

1
使用接口可能是此处的最佳解决方案。+1。
Juan Carlos Coto

由于它非常简单,因此实际上非常优雅。+1
G. Stewart

12

我知道这很老但是...

为什么不直接向该父类的静态方法抛出异常,就这样,如果不重写它,则会导致异常。


1
这无济于事,在调用静态方法时会发生异常-如果您不重写该方法,则会出现“方法不存在”错误。
英国电信

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

这似乎是最优雅的解决方案。
Alex S

最好是在运行时在编译时看到这样的内容。在生产之前更容易发现问题,因为您只需要加载文件,而无需执行代码来确定文件是否存在问题或不合格
Rahly

4

我认为抽象类/接口可以看作是程序员之间的契约。它更多地涉及事物的外观/行为方式,而不实现实际功能。从php5.0和5.1.x中可以看出,这不是阻止php开发人员这样做的自然法则,而是与其他语言的其他OO设计模式保持一致的冲动。基本上,如果一个人已经熟悉其他语言,这些想法将试图防止意外行为。


尽管与php不相关,这是另一个很好的解释:stackoverflow.com/questions/3284/…–
merkuro

3
天哪!两个打败您的人完全不在他们的意料之中!这是此线程上最有见地的答案。
西奥多·R·史密斯

5
@ TheodoreR.Smith有见识?它包含错误并且几乎是连贯的。声称抽象类“没有实现实际功能”的说法不一定是正确的,除了接口之外,这就是它们的全部要点。在声称“这不是阻止php开发人员这样做的自然法则”中,我不知道什么是“它”。在声称“基本上这些想法试图防止意外行为”时,我不知道“这些想法”或潜在的“意外行为”是什么。无论您从中获得什么见解,都对我迷失了。
Mark Amery

2

我看不出有什么理由禁止使用静态抽象函数。没有理由禁止使用它们的最好论据是Java中允许使用它们。问题是:-技术上可行吗?-是的,因为它存在于PHP 5.2中,并且存在于Java中。所以可以做到这一点。我们应该这样做吗?-有道理吗?是。实现类的一部分并将类的另一部分留给用户是有意义的。它在非静态函数中有意义,为什么对静态函数不有意义?静态函数的一种用法是不得超过一个实例(单例)的类。例如,加密引擎。它不必在多个实例中都存在,并且有防止这种情况的理由-例如,您只需保护内存的一部分不受入侵者的侵害。因此,实现引擎的一部分并将加密算法留给用户是很有意义的。这只是一个例子。如果您习惯于使用静态函数,则会发现更多信息。


3
关于5.2中存在的抽象静态方法的观点在很大程度上令人误解。首先,在5.2中引入了禁止它们的严格模式警告;您听起来像在5.2中被允许。其次,在5.2中,由于后期静态绑定尚不存在,因此不能轻易地将它们“用于实现类的一部分并将类的另一部分留给用户”
Mark Amery

0

在PHP 5.4+中使用trait:

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

并在您的课程开始时:

use StaticExample;

1
我能够放入abstract public static function get_table_name();一个特征并在我的抽象类中使用该特征而不再使用E_STRICT警告!正如我希望的那样,这仍然在子级中强制执行了静态方法的定义。太棒了!
Programster

-1

查看PHP的“后期静态绑定”问题。如果要在抽象类上放置静态方法,则可能会早些而不是稍后使用它。严格的警告告诉您避免使用损坏的语言功能,这是有道理的。


4
我认为他指的是“严格标准”警告。
雅各布·休

2
您声称后期静态绑定有哪些“问题”?您断言他们是“残破的”,这是一个大胆的主张,在这里没有证据或解释。该功能对我来说一直很好,我认为这篇文章是胡说八道。
Mark Amery 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.