Late Static Binding (static::)
debt(d5/e1/b3/t7)
Closest to 'specialist tool catches it' (d5): detection_hints lists phpstan and psalm as the tools that catch self:: used where static:: is intended. These are specialist static analysis tools, not default linters or the compiler — a developer must opt in to run them, matching the d5 anchor exactly.
Closest to 'one-line patch or single-call swap' (e1): the quick_fix is explicit — replace self:: with static:: (or vice versa) at the affected call site. This is a single-keyword substitution, firmly at e1.
Closest to 'localised tax' (b3): the choice is scoped to class hierarchies using static methods. It imposes a conceptual tax on maintainers working with OOP inheritance chains, but code outside those classes is entirely unaffected. Not wide enough for b5 but more than b1.
Closest to 'serious trap' (t7): the misconception field states directly that developers believe self:: and static:: are interchangeable — a natural assumption because both appear to reference 'the current class'. The trap contradicts how self:: behaves in other OOP languages where the equivalent keyword is dynamic, making this a cross-language surprise at t7.
Also Known As
TL;DR
Explanation
In PHP, self:: always refers to the class where the method is written, even if called on a subclass. Late static binding (PHP 5.3+) uses static:: to refer to the class actually invoked at runtime. This is essential for static factory methods and the ActiveRecord pattern, where subclasses should return instances of themselves rather than the parent. get_called_class() provides the string equivalent. Overuse of static methods (as opposed to instance methods with dependency injection) can complicate testing.
Common Misconception
Why It Matters
Common Mistakes
- Using self:: in a static method intended to be inherited — self:: always refers to the defining class, not the subclass.
- Using static:: in non-inherited static utilities where self:: is the correct and clearer choice.
- Not understanding that static:: in a non-static context refers to the class of the object, like get_class($this).
- Forgetting that late static binding only matters when the method is inherited — in a sealed class, self:: and static:: are identical.
Code Examples
class Model {
public static function create(): self { // 'self' returns Model even when called as User::create()
return new self();
}
}
class User extends Model {}
$user = User::create(); // Returns Model instance, not User!
// Fix: return new static(); — returns the called class
class Model {
// static:: refers to the class that was actually called (child)
// self:: always refers to Model — wrong for fluent factory methods
public static function create(array $data): static {
$instance = new static(); // static:: = called class
foreach ($data as $k => $v) { $instance->$k = $v; }
return $instance;
}
public static function tableName(): string {
return strtolower((new \ReflectionClass(static::class))->getShortName()) . 's';
}
}
class User extends Model {}
class Order extends Model {}
$user = User::create(['name' => 'Alice']); // returns User instance
$order = Order::create(['total' => 99]); // returns Order instance
echo User::tableName(); // 'users'
echo Order::tableName(); // 'orders'