0

Late static binding — why 'late' and why 'static' are both important words

Intermediate5 min read·eng-12-016
interview

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
<?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)."