PHP Late Static Binding

Distinguish the declaring class from the called class and design inherited factories with predictable construction.

Lesson 51 of 86Advanced10 min read

Late static binding lets an inherited method refer to the class that was called rather than only the class where the method was written. In PHP, self:: normally refers to the declaring class, while static:: can follow the called child class. That difference explains why a shared factory or label method can produce child-specific behavior without being reimplemented.

This guide traces the difference with class constants and factories, then explains when late binding creates an intentional extension point and when it can break a base class's assumptions. Review ordinary classes and inheritance first; late static binding builds on those relationships rather than replacing them.

Declaring class versus called class

A method has a source location: the class that declares its body. It also runs as part of a call that can originate through a child class. Self uses the source-level class. Static uses the late-bound called class maintained by PHP for the call chain.

That means one inherited method can read the parent's constant through self and the child's overriding constant through static. The difference is not whether the member itself is mutable or whether the method is short. It is which class context the lookup should respect.

Use self when the declaring implementation owns the rule and children should not alter that lookup. Use static when subclass specialization is part of the contract. Replacing all self expressions with static can turn implementation details into unintended extension points. Choose per operation based on the invariant you need to preserve.

get_called_class and diagnostic context

get_called_class() returns the late-bound class name from an appropriate class context. It can help explain which class an inherited static operation is serving. By contrast, __CLASS__ describes the class where the expression is written. These values often differ in inherited code.

Do not use diagnostic class names as a substitute for a clear interface. A caller should not need to inspect the runtime class string to know whether an object can calculate a total. Type against an interface or base contract and let polymorphism provide the behavior.

Dynamic class-name construction also needs care. If a factory accepts a class name from external input, late binding does not validate that name or authorize its creation. Map approved names to known classes and check the required type relationship. Arbitrary instantiation can expose constructors the endpoint never intended to run.

Inherited factories with new static

An inherited factory using new self() constructs the class where that factory is declared. A factory using new static() constructs the called class. The latter can be useful for a hierarchy whose children share a compatible construction contract.

The compatibility assumption is important. If a child changes its constructor to require extra arguments, an inherited new static factory may no longer know how to create it. The shorter factory does not invent missing dependencies. Keep constructors compatible, override the factory deliberately, or use a separate creator when construction varies.

Return types should match the factory promise. Modern PHP offers a static return type for methods intended to return the called class type, introduced in PHP 8.0. A self return type expresses the declaring type contract. Choose the type that the factory can reliably honor across the hierarchy.

Forwarding calls and explicit class calls

Late-bound context can be forwarded through calls using self, parent and static. An explicit call naming a particular class can establish a different called-class context. These rules become subtle in a deep hierarchy, which is a reason to keep inheritance shallow and explain extension points clearly.

Avoid mixing explicit parent-class calls and late-bound calls casually in the same factory workflow. Trace which class should own each step. If the behavior is difficult to explain without a long call-context table, composition may make the design simpler: a formatter or creator can receive an explicit strategy object.

The PHP late static binding manual documents forwarding and non-forwarding calls. For maintenance work, test the base and at least one child because a method may behave correctly through the base while failing through the child-specific context.

PHP Late Static Binding Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Compare both lookups in one call

The child does not declare describe. Its inherited method still has BaseLabel as the self context, but the call through ChildLabel supplies the late-bound class for static. This example isolates lookup behavior without depending on a mutable property or external input.

PHP
<?php
class BaseLabel {
    public const TEXT = 'base';
    public static function describe(): string {
        return self::TEXT . ':' . static::TEXT;
    }
}
class ChildLabel extends BaseLabel {
    public const TEXT = 'child';
}
echo BaseLabel::describe() . PHP_EOL;
echo ChildLabel::describe() . PHP_EOL;
Output
base:base
base:child
Example 2

A factory returns the called subclass

Alert shares the empty constructor contract, so the inherited factory can create it. Changing new static to new self would create Message, which would violate the static return promise on an Alert call. Keep that experiment separate and handle the expected error rather than treating it as a successful example.

PHP
<?php
class Message {
    public static function create(): static {
        return new static();
    }
}
class Alert extends Message {}
$message = Alert::create();
echo get_class($message) . PHP_EOL;
Output
Alert

Common PHP Late Static Binding Mistakes

Treating late binding as ordinary instance dispatch

Late static binding resolves class context for static-style lookups. Ordinary instance method polymorphism already selects an overriding method on an object through $object->method(). Do not introduce static methods solely to obtain polymorphism that instance methods and interfaces already express clearly.

Do not rely on static binding to make mutable static storage isolated per child. Property declarations and inheritance determine storage relationships. Lookup context and storage ownership are connected but separate questions. Use explicit stateful objects when the storage lifetime or isolation needs to be obvious.

PHP Late Static Binding in Real Projects

Practical task: child-specific export names

Create a base export class that provides one formatName method using a class constant. A CSV child overrides only the constant. The shared method should return the child's name when called through that child, while the base call keeps the base name. This is an intentional, narrow extension point.

PHP
<?php
class ExportFormat {
    protected const NAME = 'generic';

    public static function formatName(): string {
        return static::NAME;
    }
}
class CsvFormat extends ExportFormat {
    protected const NAME = 'csv';
}
echo ExportFormat::formatName() . PHP_EOL;
echo CsvFormat::formatName() . PHP_EOL;
Output
generic
csv

The constant remains protected because callers should use the documented method. Try replacing static with self and explain why both calls then return generic. If exporting requires different parsing or encoding algorithms, use methods or composed strategies rather than pretending a changed name alone supplies a complete format implementation.

PHP Late Static Binding FAQ

Does static always mean a static property?

No. Static is also a binding keyword and a return type in relevant contexts. Read the surrounding syntax.

Why does self ignore the child's constant?

Self is tied to the class where the method body is declared. Static is the deliberate late-bound lookup.

Can new static break?

Yes if the called subclass's construction requirements differ from what the factory supplies. Preserve or explicitly adapt that contract.

Should all factories return static?

No. A factory may intentionally return one fixed implementation or an interface. Declare what it truly guarantees.

How can I test this behavior?

Call the same inherited operation through the base and a child, verify the expected result type or value, and include constructor differences when the hierarchy permits them.

PHP Late Static Binding Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choiceeasy

A shared method must always use its declaring class's fixed rule. Which lookup expresses that intention?

PHP Late Static Binding Quiz

2 questions to check what you learned.

  1. Which lookup can follow the called child class?

    • self::NAME always
    • __LINE__
    • static::NAME
  2. What risk appears in an inherited new static factory?

    • A child may require constructor arguments the factory does not supply
    • Objects can never be created
    • Constants become mutable automatically