Static properties, static methods, late static binding (static::)
Concept
Static properties store data at the class level — shared across all instances. Static methods operate at the class level without a $this. Late static binding (LSB) is the mechanism that makes static:: resolve to the class that was actually called at runtime, rather than the class where the method was defined.
Static properties: Initialized once per request (or per worker in long-running processes). Every instance of the class shares the same static property. Modifying it in one instance affects all others — a global state smell, but sometimes exactly what you want (e.g., an instance count, a shared configuration store).
self:: vs static::: self:: is resolved at compile time — it always refers to the class in which the method was defined. static:: is resolved at runtime — it refers to the class from which the method was called. This distinction matters for static factory methods and static methods meant to be overridden in subclasses.
When static is appropriate: Stateless utility methods (Math::clamp), factory methods (Model::create()), registries and configuration that is intentionally shared across instances. Avoid static for anything that should be testable or injectable — static state is global state and breaks test isolation.
static::class gives the runtime class name as a string. Equivalent to get_class() from inside the class but available in static context.
Code Example
<?php
declare(strict_types=1);
// Static property — shared across all instances
class ConnectionPool
{
private static int $count = 0;
private static array $connections = [];
public static function add(mixed $conn): void
{
self::$connections[] = $conn;
self::$count++;
}
public static function getCount(): int { return self::$count; }
}
ConnectionPool::add('conn1');
ConnectionPool::add('conn2');
echo ConnectionPool::getCount(); // 2
// self:: vs static:: — the critical difference
class Base
{
public static function create(): static
{
return new static(); // late static binding — creates Child when called on Child
}
public static function className(): string
{
return static::class; // returns caller's class name
}
}
class Child extends Base {}
$base = Base::create(); // Base instance
$child = Child::create(); // Child instance ← LSB makes this work
var_dump($child instanceof Child); // true
echo Base::className(); // "Base"
echo Child::className(); // "Child" — static:: resolved at call site
// self:: always resolves to declaring class
class Singleton
{
private static array $instances = [];
public static function getInstance(): static // returns actual class
{
$class = static::class; // get the called class name
return self::$instances[$class] ??= new static();
}
}
class ChildSingleton extends Singleton {}
$a = Singleton::getInstance();
$b = Singleton::getInstance();
$c = ChildSingleton::getInstance();
var_dump($a === $b); // true — same instance
var_dump($a === $c); // false — different class → different instance