Why does PHP allow abstract static functions
Asked Answered
P

2

18

Consider the following code:

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

    abstract protected static function abstractStaticFunction();
}

ExampleClass::regularStaticFunction();

The PhpStorm IDE puts a warning on the declaration of abstractStaticFunction that reads:

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

Static function should not be abstract.

However, PHP continues program execution when parsing this class and outputs the following:

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

It seems to me that because PHP allows static function calls on abstract classes, defining an abstract static function on an abstract class should not be possible.

Why are abstract static functions allowed in PHP by the interpreter, when they are nonsensical?

Paraesthesia answered 12/1, 2017 at 10:42 Comment(2)
I have read that question and did not see an answer to my question. Why is the PHP interpreter capable of parsing abstract static functions, when they are nonsensical.Paraesthesia
Bah, this once again has a vote to close it as a duplicate of #999566. I don't understand why; it seems pretty obvious to me that a quetion asking why abstract static functions are NOT allowed is not a duplicate of this question asking why they ARE allowed. The distinction between the two is not subtle; they're asking opposite things!Persuade
F
38

This is good explanation from this answer by 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.

Fife answered 12/1, 2017 at 11:18 Comment(2)
-1; this answers what is pretty much the opposite question - the question of why abstract static functions are not permitted (in the loose sense that they trigger a warning). The OP here is instead asking why they are permitted, which this answer doesn't really address.Persuade
my version of PHP 7.4 doesn't generate E_STRICTMcdougall
P
14

Abstract static functions aren't nonsensical! In fact, they enable some designs that are, to my sensibilities, simpler and cleaner than what I'd have to resort to in a language that doesn't have them, like Java or C#.

Let's take an example. Suppose I'm writing some sort of mundane enterprisey business application that needs to synchronise some business objects between two APIs. These APIs have object models that can be mapped to each other, but use different names, and different serialisation formats.

In PHP, thanks to abstract static methods, I can define an abstract base class for these business object types that looks like this...

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

... and then every concrete subclass I create will be guaranteed to provide all the information needed to fetch it from either of the two APIs and deserialise it, or to serialize it and send it to either API. Then, later, I can write some code like this:

// 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);
    }
}

Do I strictly need abstract static methods to write the program above? No; there are other patterns I could've used, like having every model class be paired with a corresponding factory class that is responsible for instantiating it from its JSON or XML representations. But such a design is more complicated than the one I've shown above.

The answer to why they're allowed, then, is simply that they are useful, since they enable some nice, simple patterns that wouldn't be possible without them. Of course, there are also arguments against them existing, one of which was given in the question - that it's ugly to expose abstract static methods on a class given that static methods on an abstract class are callable. I don't find such considerations particularly persuasive, but even if you do, there is still a tradeoff between them and the utility that abstract static methods provide, and the PHP maintainers have presumably weighed them and opted for the side of letting abstract static methods exist.

Persuade answered 8/12, 2018 at 15:3 Comment(1)
I don't think two different APIs should be tied together with abstract classes. If they are the same then one class. If they need the same methods then two classes with an interface. If they have any difference then two classes with some mapper pre sending to the API. And one class shouldn't handle different data types from separate APIs, like json vs xml, that's what adaptors are for.Trifle

© 2022 - 2024 — McMap. All rights reserved.