为什么PHP允许抽象静态函数?

16

请考虑以下代码:

abstract class ExampleClass
{
    public static function regularStaticFunction()
    {
        return static::abstractStaticFunction();
    }

    abstract protected static function abstractStaticFunction();
}

ExampleClass::regularStaticFunction();

PhpStorm IDE提供了一个关于abstractStaticFunction声明的警告:

PHP Strict Standards: Static function 'abstractStaticFunction' should not be abstract.

静态函数不应该是抽象的。

然而,当解析此类并输出以下内容时,PHP会继续程序执行:

PHP Strict standards: Static function ExampleClass::abstractStaticFunction() should not be abstract in php shell code on line 7

我认为,因为PHP允许在抽象类上调用静态函数,定义抽象静态函数应该是不可能的。

由于它们毫无意义,为什么解释器允许在PHP中使用抽象静态函数?


重复。https://dev59.com/0nNA5IYBdhLWcg3wWsUA - German Lashevich
我已经阅读了那个问题,但没有看到我的问题的答案。为什么PHP解释器能够解析抽象静态函数,即使它们是毫无意义的呢? - Thijs Riezebeek
这真让人烦恼,这个问题又被投票关闭了,作为 https://dev59.com/0nNA5IYBdhLWcg3wWsUA 的重复。我不明白为什么会这样;对我来说,一个询问为什么不允许抽象静态函数的问题与一个询问为什么允许它们的问题是非常明显不同的。两者之间的区别并不微妙;它们在问相反的事情! - Mark Amery
2个回答

36
这是来自Mark Amery此答案的好解释:

PHP bug report 53081, called for the warning to be dropped since the addition of the static::foo() construct had made abstract static methods reasonable and useful. Rasmus Lerdorf (creator of PHP) starts off by labelling the request as bogus and goes through a long chain of bad reasoning to try to justify the warning. Then, finally, this exchange takes place:

Giorgio

i know, but:

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

Rasmus

Right, that is exactly how it should work.

Giorgio

but it is not allowed :(

Rasmus

What's not allowed?

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

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

cB::A();

This works fine. You obviously can't call self::B(), but static::B() is fine.

The claim by Rasmus that the code in his example "works fine" is false; as you know, it throws a strict mode warning. I guess he was testing without strict mode turned on. Regardless, a confused Rasmus left the request erroneously closed as "bogus".

And that's why the warning is still in the language. This may not be an entirely satisfying explanation - you probably came here hoping there was a rational justification of the warning. Unfortunately, in the real world, sometimes choices are born from mundane mistakes and bad reasoning rather than from rational decision-making. This is simply one of those times.

Luckily, the estimable Nikita Popov has removed the warning from the language in PHP 7 as part of PHP RFC: Reclassify E_STRICT notices. Ultimately, sanity has prevailed, and once PHP 7 is released we can all happily use abstract static without receiving this silly warning.


2
-1;这回答的问题几乎是相反的——为什么“抽象静态”函数被允许(在宽松的意义上,它们会触发警告)。而此处的OP则是在问为什么它们被允许,这个答案并没有真正解决这个问题。 - Mark Amery
我的 PHP 7.4 版本不会生成 E_STRICT 错误。 - Michael Quad

13

抽象静态方法并不是毫无意义的!实际上,它们使得某些设计更加简单和清晰,这在没有它们的语言(如Java或C#)中会很难实现。

让我们来看一个例子。假设我正在编写一些枯燥的企业级应用程序,需要在两个API之间同步一些业务对象。这些API具有可以相互映射的对象模型,但使用不同的名称和序列化格式。

在PHP中,由于有了抽象静态方法,我可以定义一个抽象基类来表示这些业务对象类型,代码如下...

abstract class ApiObject {
    /** The REST resource URL for this object type in the Foo API. */
    abstract static function fooApiResourceUrl();

    /** The REST resource URL for this object type in the Bar API. */
    abstract static function barApiResourceUrl();

    /** Given an XML response from the Foo API representing an object of this
        type, construct an instance. */
    abstract static function fromFooXml($xml);

    /** Given a JSON response from the Bar API representing an object of this
        type, construct an instance. */
    abstract static function fromBarJson($json);

    /** Serialize this object in the XML format that the Foo API understands */
    abstract function toFooXml();

    /** Serialize this object as JSON that the Bar API understands */
    abstract function toBarJson();
}

...然后我创建的每个具体子类都将保证提供从这两个API中获取并反序列化它所需的所有信息,或者将其序列化并发送到任何一个API所需的所有信息。然后,稍后,我可以编写如下代码:

// Ensure that all instances of these types that exist in the Foo API also
// exist in the Bar API:
$classesToSync = ['Widget', 'Frobnicator', 'Lead', 'Invoice'];
foreach ($classesToSync as $apiObjectClass) {
    $fooObjXmls = httpGetRequest($apiObjectClass::fooApiResourceUrl());
    foreach ($fooObjXmls as $fooObjXml) {
        $fooObj = $apiObjectClass::fromFooXml($fooObjXml);
        $json = $fooObj->toBarJson();
        httpPutRequest($apiObjectClass::barApiResourceUrl(), $json);
    }
}

在写上面的程序时,我是否严格需要抽象静态方法?不是的;还有其他模式可以使用,比如每个模型类都与一个对应的工厂类配对,由该工厂类负责从其JSON或XML表示中实例化它。但是这样的设计比我上面展示的更复杂。

那么它们被允许的原因很简单,它们是有用的,因为它们使一些好的、简单的模式成为可能。当然,也有反对它们存在的论点,其中一个是在问题中给出的 - 在类上公开抽象静态方法是丑陋的,因为抽象类上的静态方法是可调用的。我并不觉得这种考虑特别有说服力,但即使您这样认为,抽象静态方法提供的效用和它们之间仍存在权衡,而PHP维护者显然已经权衡了他们,并选择让抽象静态方法存在。


我认为不应该用抽象类将两个不同的API绑定在一起。如果它们是相同的,则使用一个类。如果它们需要相同的方法,则使用带有接口的两个类。如果它们有任何差异,则使用两个类并在发送到API之前使用某些映射器。而且,一个类不应该处理来自不同API的不同数据类型,比如json vs xml,这就是适配器的作用。 - James

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接