static, self and Late Static Binding

self:: is fixed at definition time; static:: resolves to the class that was actually called. That difference powers fluent factories.

Inside a class, self:: always means this exact class, resolved where the code is written. static:: means the class that was actually called at runtime — that's late static binding. The gap only shows up with inheritance, and it matters most in factory methods.

Easy example

<?php
class Base {
    public static function create(): static {
        return new static(); // late static binding
    }
}
class Child extends Base {}

var_dump(Base::create()  instanceof Base);  // true
var_dump(Child::create() instanceof Child); // true — not just Base!

Had create() used new self(), Child::create() would have handed you a Base — almost never what you want in a factory.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Build a fluent static factory on a base Model class that correctly returns subclass instances with static::class.
  • Use static:: in a base class method so each subclass's registry or cache is stored separately.
  • Implement a singleton pattern in a base class that creates one instance per concrete subclass using static::.

More examples

self:: vs static:: difference

static:: resolves to the class that was actually called; self:: would always return Base regardless of the subclass.

Example · php
<?php
class Base {
    public static function create(): static {
        return new static();
    }
    public static function className(): string {
        return static::class; // late static binding
    }
}

class Child extends Base {}

$obj = Child::create();
echo get_class($obj);        // Child
echo Child::className();     // Child
echo Base::className();      // Base

Fluent builder with LSB

Returning static ensures the fluent chain preserves the subclass type, keeping UserQuery a UserQuery throughout.

Example · php
<?php
class QueryBuilder {
    protected array $wheres = [];

    public function where(string $clause): static {
        $clone = clone $this;
        $clone->wheres[] = $clause;
        return $clone;
    }
    public function toSql(): string {
        $w = implode(' AND ', $this->wheres);
        return 'SELECT * FROM table' . ($w ? " WHERE $w" : '');
    }
}

class UserQuery extends QueryBuilder {}

$q = (new UserQuery())->where('active = 1')->where('role = "admin"');
echo get_class($q);  // UserQuery — not QueryBuilder
echo $q->toSql();

Per-subclass static registry

Keying the static property by static::class gives each subclass its own isolated registry without extra code.

Example · php
<?php
class Registry {
    private static array $items = [];

    public static function register(string $key, mixed $value): void {
        static::$items[static::class][$key] = $value;
    }
    public static function get(string $key): mixed {
        return static::$items[static::class][$key] ?? null;
    }
}

class PluginRegistry extends Registry {}

Registry::register('db', 'mysql');
PluginRegistry::register('db', 'sqlite');

echo Registry::get('db');       // mysql
echo PluginRegistry::get('db'); // sqlite

Discussion

  • Be the first to comment on this lesson.