Late static binding — why 'late' and why 'static' are both important words
Concept
Late Static Binding vocabulary lesson — why BOTH words "late" and "static" matter, and what each contribution to the meaning.
This is a vocabulary entry focused on the TERM itself. The implementation is covered in eng-09-008.
"Static" in late static binding refers to static methods and the class context — not instance methods. Static methods are class-level, not instance-level. They're called on a class, not on an object.
The problem LSB solves: When a static method is defined in a parent class and called on a subclass, self:: always resolves to the parent class (where the method was written). But static:: resolves to the class that was actually called.
"Late" means the binding happens LATE — at runtime, not at compile time. In contrast, self:: is an early binding — the compiler knows at parse time that self:: in ParentClass means ParentClass, regardless of which subclass calls the method.
Analogy:
self::is like writing your address in permanent ink — it never changes.static::is like a GPS that updates to show your current location — it changes depending on where you are when you look.
The word "static" is confusing here: LSB is about static methods/context, but the binding itself is DYNAMIC (runtime). PHP chose the keyword static:: to mean "the class that was statically called," not "something that's statically fixed." This is a naming quirk that trips up beginners.
Interview signal: Explaining self:: vs static:: concisely shows deep PHP knowledge.
Code Example
<?php
// WHY "LATE" — resolved at runtime, not compile time
class Model
{
public static function create(): static
{
// At COMPILE TIME: PHP doesn't know which class will call this
// At RUNTIME (LATE): PHP checks which class was called, returns that
return new static(); // "late" — decided at call time
}
public static function selfCreate(): self
{
return new self(); // "early" — decided at definition time: always Model
}
}
class User extends Model {}
$model = Model::create(); // Model instance — static:: → Model at runtime
$user = User::create(); // User instance — static:: → User at runtime (LATE!)
$also = User::selfCreate(); // Model instance — self:: → always Model (EARLY)
// WHY "STATIC" — we're in a static context (class, not instance)
class Registry
{
private static array $instances = [];
public static function getInstance(): static
{
$class = static::class; // runtime class name — 'Registry' or 'UserRegistry'
if (!isset(static::$instances[$class])) {
static::$instances[$class] = new static();
}
return static::$instances[$class];
}
}
class UserRegistry extends Registry {}
class ProductRegistry extends Registry {}
$ur = UserRegistry::getInstance(); // UserRegistry singleton
$pr = ProductRegistry::getInstance(); // ProductRegistry singleton — separate instance!
// If we'd used self:: instead of static::, BOTH would get Registry instances
// INTERVIEW EXPLANATION (concise):
// "self:: is bound at definition time — it always refers to the class where the
// method was written. static:: is bound at call time (late) — it refers to the
// class that was actually invoked. The 'static' in late static binding refers to
// the static method context, not to the type of binding (which is actually dynamic)."