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 /* ... */ }
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With