12

With late static binding in PHP v5.3, one can usefully declare static methods in interfaces; with traits in PHP v5.4, methods can be either static or abstract but not both. This appears to be illogical and inconsistent.

In particular, suppose one has an interface for which a trait provides all implementation, except for a static method; unless that method is declared in the trait, static analysers balk at any references thereto from within the trait. But providing a concrete implementation within the trait no longer forces implementing/using classes to provide their own implementation—which is dangerous; abstract static would be ideal, but is not allowed.

What is the explanation for this contradiction? How would you recommend resolving this problem?

interface MyInterface
{
    public static function getSetting();
    public function doSomethingWithSetting();
}

trait MyTrait
{
    public abstract static function getSetting(); // I want this...

    public function doSomethingWithSetting() {
        $setting = static::getSetting(); // ...so that I can do this
        /* ... */
    }
}

class MyClass implements MyInterface
{
    use MyTrait;
    public static function getSetting() { return /* ... */ }
}
eggyal
  • 113,121
  • 18
  • 188
  • 221
  • I tried what you want in php 5.6.10 (CLI) and it worked.. Did I get you wrong or do you want this for php 5.4? – Mjh Sep 24 '15 at 13:11
  • 1
    @Mjh: What is your [`error_reporting`](https://secure.php.net/manual/en/errorfunc.configuration.php#ini.error-reporting) setting? The above should give rise to an `E_STRICT` error. – eggyal Sep 24 '15 at 13:13
  • I set the `error_reporting(E_ALL);`. I declared a trait with `public abstract static function test();` and I got `PHP Strict Standards: Static function MyTest::test() should not be abstract in php shell code on line 1`. When I create a class that uses the trait and if I don't implement the abstract method then I get `PHP Fatal error: Class MyTestClass contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (MyTestClass::test) in php shell code on line 1`. Does that help at all? – Mjh Sep 24 '15 at 13:20
  • 1
    Any relation to this answer? http://stackoverflow.com/questions/999066/why-does-php-5-2-disallow-abstract-static-class-methods – al'ein Sep 24 '15 at 13:20
  • 1
    @AlanMachado: Yes, definitely related... I'm reading that now—thanks – eggyal Sep 24 '15 at 13:22
  • 1
    @AlanMachado: Whilst that's undoubtedly the source of this error, I do think it's flawed reasoning to apply those same arguments (regarding static inheritance) to traits. So I think I'll hold off closing this as a dupe of that other question—it definitely provides good context/background reading, though. Thank you (again). – eggyal Sep 24 '15 at 13:35
  • And I see your point, I'm marking this question in case you get an answer. Good luck! – al'ein Sep 24 '15 at 13:37
  • 1
    This goes somewhat against the grain of the question, but in this particular case, why restrict the implementor to a `static` method in the first place? It doesn't make any difference to the trait whether it calls a `static` method or not, and since you're actually explicitly calling it from a non-static method, there's no reason to force `getSetting` to be `static`. That leaves the implementor less freedom, for example to return settings based on instance variables. – deceze Sep 24 '15 at 15:14

1 Answers1

8

TL;DR: As of PHP 7, you can. Before then, you could define abstract static on trait, but internals deemed it bad practice.


Strictly, abstract means sub-class must implement, and static means code for this specific class only. Taken together, abstract static means "sub-class must implement code for this specific class only". Totally orthogonal concepts.

But... PHP 5.3+ supports static inheritance thanks to LSB. So we actually open that definition up a bit: self takes the former definition of static, while static becomes "code for this specific class or any of its sub-classes". The new definition of abstract static is "sub-class must implement code for this specific class or any of its sub-classes". This can lead some folks, who think of static in the strict sense, to confusion. See for example bug #53081.

What makes trait so special to elicit this warning? Well, take a look at the engine code that implements the notice:

if (ptr->flags & ZEND_ACC_STATIC && (!scope || !(scope->ce_flags & ZEND_ACC_INTERFACE))) {
    zend_error(error_type, "Static function %s%s%s() cannot be abstract", scope ? ZSTR_VAL(scope->name) : "", scope ? "::" : "", ptr->fname);
}

That code says the only place an abstract static is allowed is within an interface. It's not unique to traits, it's unique to the definition of abstract static. Why? Well, notice there's a slight corner case in our definition:

sub-class must implement code for this specific class or any of its sub-classes

With this code:

abstract class Foo {
    abstract public static function get();
}

That definition means I should be able to call Foo::get. After all Foo is a class (see that keyword "class" there) and in the strict definition, get is meant to be implemented in that class Foo. But clearly that makes no sense, because well, we're back to the orthogonality of strict static.

If you try it in PHP, you get the only rationale response possible:

Cannot call abstract method Foo::get()

So because PHP added static inheritance, it has to deal with these corner cases. Such is the nature of features. Some other languages (C#, Java, etc.) don't have this problem, because they adopt the strict definition and simply don't allow abstract static. To get rid of this corner case, and simplify the engine, we may enforce this "abstract static only in interface" rule in the future. Hence, E_STRICT.


I would use a service delegate to solve the problem:

I have common method I want to use in several classes. This common method relies on a static method that must be defined externally to the common code.

trait MyTrait
{
    public function doSomethingWithSetting() {
        $service = new MyService($this);
        return $service->doSomethingWithSetting();
    }
}

class MyService
{
    public function __construct(MyInterface $object) {
        $this->object = $object;
    }
    public function doSomethingWithSetting() {
        $setting = $this->object->getSetting();
        return $setting;
    }
}

Feels a bit Rube Goldberg though. Probably would look at the motivation for statics and consider refactoring them out.

bishop
  • 32,403
  • 9
  • 89
  • 122
  • I agree with your analysis of `abstract` vs `static` and its contradictory meaning within the context of *static inheritance*. However, when a *trait* declares a method to be `abstract`, it still *can* be implemented within the using class—just like `static` methods declared in interfaces can be implemented within the implementing class. So the contradiction you highlight no more stands for traits than it does for interfaces, where such usage is allowed. Citing the engine code that causes the error only reinforces that this is an oversight/bug, not that it is a deliberate design decision. – eggyal Sep 26 '15 at 18:20