Late Static Binding
What Is Late Static Binding?
In simple terms: When a parent class contains a static method like create(), and a child class inherits it, the question is: when Child::create() runs, should self mean the parent (where the code was written) or the child (the class that was called)? Late static binding gives you a way to refer to the class that was actually called: the keyword static.
Technically: self:: is resolved at compile time to the class in which the method is defined. static:: is resolved at runtime ("late") to the class name used in the call — the "late static binding". It works with static methods, static properties, constants, new static(), and the static return type. Instance methods also benefit: inside them, static:: refers to the object's actual class.
| Reference | Resolves To | Decided |
|---|---|---|
self:: | The class where the code is written | At compile time |
static:: | The class that was called (or the object's class) | At runtime |
parent:: | The parent of the class where the code is written | At compile time |
The Problem With self
<?php
class Model
{
public static function create(): self
{
return new self();
}
}
class User extends Model {}
$user = User::create();
echo get_class($user) . "\n";
Output
Model
User::create() was called, but new self() always creates a Model, because self was fixed to Model when the method was written. A factory inherited by child classes therefore produces the wrong type.
The Solution: static
<?php
class Model
{
protected array $attributes = [];
public static function create(array $attributes = []): static
{
$model = new static();
$model->attributes = $attributes;
return $model;
}
public function get(string $key): mixed
{
return $this->attributes[$key] ?? null;
}
}
class User extends Model {}
class Product extends Model {}
$user = User::create(['name' => 'Asha']);
$product = Product::create(['sku' => 'PEN-01']);
echo get_class($user) . ': ' . $user->get('name') . "\n";
echo get_class($product) . ': ' . $product->get('sku') . "\n";
Output
User: Asha
Product: PEN-01
How It Works
new static()creates an instance of the class that was called:UserforUser::create(),ProductforProduct::create().- The return type
static(PHP 8.0+) tells PHP and IDEs that the method returns an instance of the called class, soUser::create()is known to return aUser. - The factory is written once in
Modeland works correctly for every subclass.
Static Properties and Constants
Late static binding lets a parent class read values that child classes customize:
<?php
abstract class Model
{
protected static string $table = '';
public const PRIMARY_KEY = 'id';
public static function findSql(): string
{
return sprintf('SELECT * FROM %s WHERE %s = ?', static::$table, static::PRIMARY_KEY);
}
public static function selfTable(): string
{
return "'" . self::$table . "'";
}
}
final class User extends Model
{
protected static string $table = 'users';
}
final class Product extends Model
{
protected static string $table = 'products';
public const PRIMARY_KEY = 'sku';
}
echo User::findSql() . "\n";
echo Product::findSql() . "\n";
echo User::selfTable() . "\n";
Output
SELECT * FROM users WHERE id = ?
SELECT * FROM products WHERE sku = ?
''
static::$tableandstatic::PRIMARY_KEYuse the values of the called class.self::$tablereadsModel's own (empty) value, regardless of which class was called.
This is the mechanism behind Active Record ORMs such as Laravel's Eloquent, where User::find(7) and Product::find('PEN-01') share one implementation in a base Model class, but each subclass knows its own table.
static:: in Instance Methods
In instance methods, static:: refers to the class of the object:
<?php
class Notification
{
protected const CHANNEL = 'email';
public function describe(): string
{
return static::class . ' via ' . static::CHANNEL;
}
}
final class SmsNotification extends Notification
{
protected const CHANNEL = 'sms';
}
echo (new Notification())->describe() . "\n";
echo (new SmsNotification())->describe() . "\n";
Output
Notification via email
SmsNotification via sms
static::class returns the name of the actual class, which is useful for logging, cache keys, and error messages. (get_called_class() is an older function returning the same value in static context.)
Forwarding and Non-Forwarding Calls
When a static method calls another static method, PHP decides whether the "called class" information is passed along:
| Call Style | Forwards the Called Class? |
|---|---|
parent::method() | Yes |
self::method() | Yes |
static::method() | Yes |
ClassName::method() | No — ClassName becomes the called class |
<?php
class Base
{
public static function who(): string
{
return static::class;
}
}
class Child extends Base
{
public static function test(): string
{
return implode(' / ', [parent::who(), self::who(), Base::who()]);
}
}
echo Child::test() . "\n";
Output
Child / Child / Base
parent::who() and self::who() keep Child as the called class, so static::class inside who() is Child. Calling Base::who() by name starts a new call with Base as the called class.
Fluent Methods With static Return Types
The static return type also matters for instance methods that return $this, ensuring correct types in subclasses:
<?php
class QueryBuilder
{
protected array $conditions = [];
public function where(string $condition): static
{
$this->conditions[] = $condition;
return $this;
}
public function toSql(string $table): string
{
return "SELECT * FROM $table" . ($this->conditions ? ' WHERE ' . implode(' AND ', $this->conditions) : '');
}
}
final class ProductQuery extends QueryBuilder
{
public function inStock(): static
{
return $this->where('stock > 0');
}
}
echo (new ProductQuery())->where("category = 'stationery'")->inStock()->toSql('products') . "\n";
Output
SELECT * FROM products WHERE category = 'stationery' AND stock > 0
Because where() is declared to return static, calling it on a ProductQuery returns a ProductQuery, so the chain can continue with the subclass method inStock(). With a return type of self (meaning QueryBuilder), static analysis tools and IDEs would report inStock() as unknown after where().
(Building SQL with string concatenation is shown only to illustrate chaining; real query builders use bound parameters to prevent SQL injection — see SQL Injection Prevention.)
When to Use self and When static
Use self When | Use static When |
|---|---|
The class is final (no subclasses can exist) | Subclasses may override the method, property, or constant |
| The exact class's value is required, even in subclasses | The behavior should adapt to the called class |
| Referring to private members (not visible to subclasses anyway) | Implementing inheritable factories (new static()) |
In final classes, self and static behave identically; self is common there.
Common Mistakes
Using new self() in Inheritable Factories
Effect: Child classes receive parent instances.
Fix: Use new static() with a static return type.
new static() With Incompatible Child Constructors
Effect: If a child class requires constructor arguments that the factory does not pass, new static() fails with an ArgumentCountError.
Fix: Keep constructors compatible, or make the base constructor final so subclasses cannot change its signature.
Expecting self:: to See Overridden Values
Effect: The parent's constant or static property is used instead of the child's.
Fix: Use static:: for values that subclasses customize.
Calling the Parent by Name
Effect: Base::method() loses the called-class information.
Fix: Use parent::method() to forward the call.
Best Practices
- Use
new static()and thestaticreturn type in factories meant to be inherited. - Read overridable constants and static properties with
static::. - Return
staticfrom fluent methods in classes that may be extended. - Use
parent::(not the class name) to call overridden static methods. - Prefer
selfin final classes and for members that must not vary.
Official reference: https://www.php.net/manual/en/language.oop5.late-static-bindings.php