Usages of PHP Static variablesUsages of PHP Static variables

I thought static variables were a lesser known PHP feature. Then, I counted about 30% of PHP projects using it, and I realized it was a more common feature than I thought. If you haven’t encountered then, they are distinct from static properties. Within a method, it is possible to declare a static variable. Static variables are kept alive between two calls of the method, allowing continuation between multiple calls.

At first glance, static variables can act as a simple cache system: store something in it on the first call, and then, reuse it later. And, for the most part, this is their actual use.

On the other hand, such a general description doesn’t give an accurate view of the various cases addressed by static variables. The underlying reasons vary widely, and they are often not characterized as a simple cache. So, let’s cover a number of them, to understand why almost 30% of PHP code use and love static variables.

Memoization

The first and direct application of static variables is memoization. It is a cache, that prevents recomputing the same value multiple times. Since a static variable is available across multiple calls, it is possible to avoid speed up multiple requests.

<?php

function getExpensiveData(): ?Result {
    static $cache = null;

    if ($cache === null) {
        $cache = verySlowDatabaseQuery(); // runs only once
    }

    return $cache;
}

?>

The advantage of static variables is obvious here: the set up is very simple, and the value is then available everytime the method is called.

In terms of memory, the variable is only allocated when the method is called the first time. This means that when the method is not used, no memory is used. This compares to setting the same cache in a property, static or not, within the class: a property is allocated at object creation, and does not require any usage.

Lazy Singleton Inside a Function

This situation is not a cache, but it happens when the function requires a service, which has to be initialized before usage. Here, the default value of the static variable helps triggering the early configuration. Later, the service is used without rebuilding it, saving a lot of processing.

<?php

function logMessage($msg) {
    static $logger = null;
    
    if ($logger === null) {
        $logger = new Logger('/tmp/app.log');
    }
    $logger->write($msg);
}

?>

In this situation, the function must be autonomous in its fetching the service. Dependency injection is usually prefered, as it gives more control over the service, in particular for testing purposes.

Complex initialization

Before PHP 8.1, it was not possible to call another method to process the initialization of the variables. Hence, one had to rely on a default variable to detected this first call and act upon it.

<?php

function getEnvironment(string $name) {
     static $env = null;
     
     if ($env === null) {
        $env = $_SERVER['myConf'] ?? getenv('myConf');
     }
     
     return $env[$name] ?? '';

}

?>

Note that such application changed since PHP 8.1. Static variables, which are variables, unlike static properties, accept any expression. That is the same as splitting the definition of the static variables in two expression: one for the static, one for the definition. On the other hand, class properties require a constant expression, as they can’t handle variables nor functioncalls.

A special first usage of the method

We just saw that methods may need to set up a service upon the first call. This time, the first usage of the method is just a special case, which requires some special behavior. It may be more than a service fetch. Basically, the first call is handled differently.

<?php

function showAlertOnce(string $alert) {
    static $shown = false;

    if (!$shown) {
            echo $alert;
            $shown = true;
       return;
    }
    
    return;
}
?>

More complex scenarii include skipping the n first calls before activating some behavior. The static variable allows for storing state across multiple calls.

Recursive Function State

Beyond memoization, the variable content may actually changes between two calls. This is the case of recursive functions. These functions need to keep a counter, whose value change at every nested call. With a static variables, the depth is entirely hidden inside the function, no need to pass then around.

<?php

function flattenArray(array $array, &$result = null) {
    static $depth = 0;
    $depth++;
    
    if ($depth >= 10) { return []; } // stop at 10 levels
    
    foreach ($array as $item) {
        if (is_array($item)) {
            flattenArray($item, $result);
        } else {
            $result[] = ['value' => $item, 'depth' => $depth];
        }
    }
    $depth--;
    return $result ?? [];
}

?>

Here, the static variable may be replaced by an extra parameter and a default value. This gives some control over the initialization of the recursion.

Avoiding Recursive Infinite Loops

This case is the opposite of the previous one: the static variable acts as a guard flag, that prevents reentry. This means that the function can be used once, but cannot be used again while being already in use.

<?php

function processNode($node) {
    static $processing = false;

    if ($processing) {
        return; // prevent recursion
    }

    $processing = true;
    // ... recursive logic ...
    $processing = false;
}

?>

Single-call methods

We’ll finish by going from infinite loop to single call. As much as methods are supposed to be called when needed, sometimes they have to be constrained and limited to one call. There, the static method allows for such level of control.

<?php

function initSystem() {
    static $initialized = false;
    
    if (!$initialized) {
        doHeavySetup();
        $initialized = true;
    }
    // rest of function...
}

?>

This may also be done with a global variable, so that other parts of the application may check if the initialization has been done or not. And, since anyone may update the global variable, it feels safer to avoid them. Static variable keeps it private to the function.

Important Caveats

Besides their clear advantages, static variables tend to have some limitations that modern PHP applictions avoid as much as possible:

  • Static variables are not testable: static state persists across calls, making unit tests pollute one another, unless when they are run in isolation.
  • Static variables are great for request‑only lifetime: they reset after the HTTP request ends. They may become a burden on long run application, like CLI or frankenPHP server applications.
  • Static variables are hard to debug: they are private to a method, and there is no way to observe them
  • Static variables may be replaced by clearer alternatives: they may be replaced by a property, a static property, an argument by reference or a global variables. In all cases, they become accessible from the outside. The good news is that the refactorisation is not too hard.

When Are Static ariables A Good Choice?

Static variables are a good fit for small scripts, short lived run, or throw away script. Either it runs, and restarts, often, or it is not used after a short amount of time.

They are good when the covered code itself is small, and battle tested. This applies the philosophy: “If it ain’t broken, don’t fix it”. For example, when debugging this piece of code is never considered, it is good to keep all its internals under wrap, including static variables.

Rarely, static variables are good to same some memory. This one is usually harder to justify.

Static Variables and their usage

Beyond simple memoization and cache, static variables provide a quick tool to set up a local value that survives between function calls. They may be easily replaced by all almost all other data container: argument, properties, static properties, global variables. Since the refactor is rather easy, it is not a major problem.

In fact, one last argument against them is that moving a variable to a property allows it to be typed. Static variables are not typed, and they may even change type during their lifetime. This is nice for a cache, but not so nice in other situations.