PHPLate Static Binding

Late Static Binding

Inside a class method, self:: always refers to the class the method was written in — the class that literally contains that line of code — no matter which subclass actually ends up calling it. That rule is simple and predictable, but it becomes a problem the moment a parent class method is supposed to produce an object of whichever class actually called it, such as a factory method inherited by several subclasses. Late static binding is PHP's answer: the static:: keyword, which resolves at call time to the class that was originally invoked, not the class where the method is defined. This page works through the difference with a concrete example, and then the pattern where this distinction matters most in real code.

The problem: self:: is frozen at definition time

Consider a base class with a static method that creates a new instance using self::. If a subclass inherits that method without overriding it, you might expect calling it on the subclass to produce a subclass instance — but self:: ignores who called it and always points back to the class it was written in.

self:: always resolves to the defining class

PHP
<?php
class Model
{
    public static function create(): self
    {
        return new self();
    }
}

class User extends Model
{
}

$user = User::create();

echo get_class($user), "\n";
Model

Even though User::create() was called on User, the object that comes back is a plain Model, not a User. self:: inside create() is resolved against Model, the class where the method body physically lives, because that's the only information self:: ever uses. User::create() merely borrows the inherited method; it doesn't change what self means inside it.

The fix: static:: resolves to the called class

Replacing self:: with static:: tells PHP to look at the class that was actually used to make the call — formally, "the class named in the initial invocation" — rather than the class where the method is defined. This lookup happens at runtime, which is why the mechanism is called late static binding, as opposed to self::, which is resolved early, at compile time, based purely on where the code sits in the file.

static:: resolves to the calling class instead

PHP
<?php
class Model
{
    public static function create(): static
    {
        return new static();
    }
}

class User extends Model
{
}

class Product extends Model
{
}

$user = User::create();
$product = Product::create();

echo get_class($user), "\n";
echo get_class($product), "\n";
User
Product

Same method body, inherited unchanged by both subclasses, but each call now produces an instance of the class that actually made the call. User::create() builds a User; Product::create() builds a Product. Nothing about create() itself needed to know those class names in advance.

The same distinction applies to static properties and other static calls

Late static binding isn't limited to new static(). Calling static::someMethod() or reading static::$someProperty from inside another static method follows the same "resolve against the called class" rule, which matters whenever a base class wants a subclass to be able to override a piece of shared logic.

static:: also affects which overridden method actually runs

PHP
<?php
class Shape
{
    public static function describe(): string
    {
        return 'A ' . static::label();
    }

    public static function label(): string
    {
        return 'generic shape';
    }
}

class Circle extends Shape
{
    public static function label(): string
    {
        return 'circle';
    }
}

echo Shape::describe(), "\n";
echo Circle::describe(), "\n";
A generic shape
A circle

describe() is defined once, on Shape, and calls static::label() rather than self::label(). When Circle::describe() runs, static resolves to Circle, so PHP picks Circle::label() even though describe() itself lives entirely inside Shape. Had describe() used self::label() instead, both calls would have printed "A generic shape," because self:: would have stayed pinned to Shape regardless of which subclass called it.

Named constructors: the pattern that needs static::

The most common real-world use of static:: is the "named constructor" (also called a factory method) pattern — a static method with a descriptive name that builds and returns an instance, used instead of (or alongside) a plain new ClassName() call. Named constructors read better at call sites, since fromArray() or fromJson() documents what the input represents, and they can run extra setup logic before returning the object. When these live on a shared base class and get inherited by many subclasses, static:: is what keeps each one producing the right subclass.

A base class with an inherited named constructor

PHP
<?php
class Record
{
    protected array $attributes = [];

    public static function fromArray(array $data): static
    {
        $record = new static();
        $record->attributes = $data;
        return $record;
    }

    public function get(string $key)
    {
        return $this->attributes[$key] ?? null;
    }
}

class Invoice extends Record
{
    public function total(): float
    {
        return (float) $this->get('total');
    }
}

$invoice = Invoice::fromArray(['total' => 149.50, 'client' => 'Acme']);

echo get_class($invoice), "\n";
echo $invoice->total(), "\n";
Invoice
149.5

Record defines fromArray() exactly once, but every subclass that inherits it gets a factory method that returns its own type. If fromArray() had used new self() instead, calling Invoice::fromArray(...) would silently hand back a plain Record with no total() method available, which is a bug that only shows up the first time you try to call a subclass-specific method on the result.

self:: inside an inherited static method is a common source of subtle bugs
The failure mode is quiet: the code runs without errors, `new self()` still produces a real, valid object, and nothing looks wrong until you call a method that only exists on the subclass — at which point PHP reports a call to an undefined method on a class you never intended to instantiate. If a static method is meant to be inherited and produce "whatever class called me," use `static::` (or the `static` return type) rather than `self::`, every time.
  • self:: resolves to the class where the code is physically written, decided at compile time.

  • static:: resolves to the class that was actually called at runtime, which is what "late" static binding refers to.

  • The distinction only matters when a method is inherited without being overridden; if a subclass redefines the method itself, there is nothing to resolve late.

  • The static return type hint (PHP 8+) documents that a method returns an instance of "whatever class you called this on," pairing naturally with new static().

  • Named constructors / factory methods on a shared base class are the most common place this distinction shows up in everyday code.

Tip
Default to `static::` (and the `static` return type) inside any method you expect subclasses to inherit unchanged, and reserve `self::` for the rare case where you deliberately want to force the defining class regardless of who calls it — for example, a private helper that only ever makes sense scoped to that one class. Treating `static::` as the default, rather than something you reach for only when a bug forces you to, avoids the entire class of "why did this factory return the wrong type" problems before they happen.