The behavior of "static" method variables in PHP is fairly subtle, and not entirely obvious to me from the documentation. Empirically, it appears:
- Method variables declared static have (at least?) two possible scopes: per-subclass, or global.
- Most of the time, static method variables are per-subclass.
- static method variables in methods named __call() invoked through PHP magic dispatch are always global.
- static method variables in private methods are global until PHP 7.4.0, and per-subclass after that.
I can't immediately find any PHP bugs describing these behaviors, or any description in the PHP 7.4.0 changelog about this.
Default Behavior
In PHP, the scope of a static method variable like this:
public function m() { static $x; // ... }
...is normally per-subclass. For example, if class B extends class A, each class gets its own copy of $x:
<?php class A { public function m() { static $x; if ($x === null) { $x = get_class($this); } return $x; } } class B extends A { } $a = new A(); $b = new B(); echo $a->m()."\n"; echo $b->m()."\n";
This emits:
This is consistent across all versions of PHP, and useful if you want to compute a map of something like the class's properties.
Behavior of __call()
What if, instead of naming this method m(), you name it __call() and invoke it by calling a method with no definition?
<?php class A { public function __call($method, $arguments) { static $x; if ($x === null) { $x = get_class($this); } return $x; } } class B extends A { } $a = new A(); $b = new B(); echo $a->m()."\n"; echo $b->m()."\n";
Since there is no m() method, this calls to ...->m() invoke __call(...). This emits:
...in all versions of PHP. That is, static variables in __call() do not get per-subclass scoping.
Note that you can "fix" this by moving the static variable to any other method (but: see below!) and calling that method from __call(). For example:
<?php class A { public function __call($method, $arguments) { return $this->n(); } public function n() { static $x; if ($x === null) { $x = get_class($this); } return $x; } } class B extends A { } $a = new A(); $b = new B(); echo $a->m()."\n"; echo $b->m()."\n";
This emits:
A
B
...in all versions of PHP.
Also note that if you call __call(...) directly, rather than using magic dispatch, you get per-subclass scoping behavior.
Behavior of private
What if, instead of defining public m(), we put the static variable in a private method?
<?php class A { public function m() { return $this->n(); } private function n() { static $x; if ($x === null) { $x = get_class($this); } return $x; } } class B extends A { } $a = new A(); $b = new B(); echo $a->m()."\n"; echo $b->m()."\n";
This emits:
A
B
PHP >= 7.4.0, https://3v4l.org/6bQWKA
...and:
A
A
PHP < 7.4.0, https://3v4l.org/6bQWKA
That is:
- Until PHP 7.4.0, a static variable in a private method has no scoping behavior. A static variable in a protected or public method is scoped per-subclass.
- In PHP 7.4.0 and later, method visibility no longer impacts the scoping of static variables.