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
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
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
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
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::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
staticreturn type hint (PHP 8+) documents that a method returns an instance of "whatever class you called this on," pairing naturally withnew static().Named constructors / factory methods on a shared base class are the most common place this distinction shows up in everyday code.