0

Static properties, static methods, late static binding (static::)

Intermediate5 min read·php-07-010
interview

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
<?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