0

Static functions and static local variables

Intermediate5 min read·php-06-013

Concept

Static functions are defined with the static keyword on a class method. They belong to the class itself, not to any instance — you call them with ClassName::method() without needing an object. They cannot access $this.

Static local variables are different: they're regular (non-method) function variables declared with static $varName. A static local variable retains its value between calls to the same function within the same PHP request. Unlike regular local variables, it is not initialized fresh on each call — it persists.

When static functions are appropriate: utility/helper functions with no state that are logically grouped with a class (e.g., DateTime::createFromFormat()), factory methods (User::fromRequest()), and the singleton pattern (though DI is preferred). Laravel uses static methods extensively for its facades and fluent APIs.

Static local variable use cases: Counters, caches within a function, "call-once" initialization (lazy initialization without a class), debug call counting. They're a lightweight form of state that doesn't require passing state through parameters or using a class. Be aware they can make functions harder to test — functions using static locals are not pure.

Static vs singleton: A static local for caching is lightweight. A full singleton class adds boilerplate. For simple single-request caching, static locals are fine. For multi-dependency objects that need injection, use a proper container.

Code Example

php
<?php
declare(strict_types=1);

// Static method — no instance needed
class MathHelper
{
    public static function clamp(float $value, float $min, float $max): float
    {
        return max($min, min($max, $value));
    }

    public static function lerp(float $a, float $b, float $t): float
    {
        return $a + ($b - $a) * $t;
    }
}
echo MathHelper::clamp(150.0, 0.0, 100.0); // 100.0
echo MathHelper::lerp(0.0, 10.0, 0.5);     // 5.0

// Factory static method pattern
class Color
{
    private function __construct(
        public readonly int $r,
        public readonly int $g,
        public readonly int $b,
    ) {}

    public static function fromHex(string $hex): self
    {
        $hex = ltrim($hex, '#');
        return new self(
            hexdec(substr($hex, 0, 2)),
            hexdec(substr($hex, 2, 2)),
            hexdec(substr($hex, 4, 2)),
        );
    }
    public static function fromRgb(int $r, int $g, int $b): self
    {
        return new self($r, $g, $b);
    }
}
$red = Color::fromHex('#ff0000');

// Static local variable — counter
function callCount(): int
{
    static $count = 0; // initialized ONCE to 0, then persists
    return ++$count;
}
echo callCount(); // 1
echo callCount(); // 2
echo callCount(); // 3

// Static local — lazy initialization / memoization
function getExpensiveConfig(): array
{
    static $config = null;
    if ($config === null) {
        $config = loadFromDatabase(); // only called once per request
    }
    return $config;
}

// Static local — debug tracer
function trace(string $label): void
{
    static $startTime = null;
    if ($startTime === null) {
        $startTime = microtime(true);
    }
    $elapsed = round((microtime(true) - $startTime) * 1000, 2);
    echo "[$elapsed ms] $label\n";
}