Late Static Binding

La Lakshmi Priya Updated 19 Oct 2026
6 min read ·Lesson 141 of 143

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.

ReferenceResolves ToDecided
self::The class where the code is writtenAt 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 writtenAt 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

  1. new static() creates an instance of the class that was called: User for User::create(), Product for Product::create().
  2. The return type static (PHP 8.0+) tells PHP and IDEs that the method returns an instance of the called class, so User::create() is known to return a User.
  3. The factory is written once in Model and 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::$table and static::PRIMARY_KEY use the values of the called class.
  • self::$table reads Model'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 StyleForwards 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 WhenUse 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 subclassesThe 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 the static return type in factories meant to be inherited.
  • Read overridable constants and static properties with static::.
  • Return static from fluent methods in classes that may be extended.
  • Use parent:: (not the class name) to call overridden static methods.
  • Prefer self in final classes and for members that must not vary.

Official reference: https://www.php.net/manual/en/language.oop5.late-static-bindings.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning
Ask about this post
AI Ask about this post

Ask questions about Late Static Binding and get answers drawn from it.

Signed-in readers only.