skip to content

In PHP, why does a page-view counter kept in a static variable reset on every request to a website?

level: juniorimportance: must knowfreq 72%

answer

  1. share-nothing request model
  2. static lives for one request only
  3. memory freed at request shutdown
  4. CLI run is one long request
  5. persist in a database or file

basics

~20 s

PHP runs every web request share-nothing: variables, statics, globals and objects are created for that request and freed when it ends. A static counter starts from its initial value on each page view, so real counts need an external store.

solid answer

~40 s

PHP runs web requests **share-nothing**: each request starts with an empty user-land state, runs the script, and at shutdown the engine destroys every variable, `static` local, static property, global and object it created. A `static $views = 0;` inside a function keeps its value only between calls **within the same request**; the next page view starts again at `0`. This is true even though a PHP-FPM or Apache worker process is reused, because the engine resets the request's state, not the process. To count visitors you write to something outside the request: a database row incremented atomically, a file under a lock, or a session for per-visitor data. The same code behaves differently under the CLI, where one run is one request, so a static survives for the whole script.

code

php · 11 lines
php
<?php
declare(strict_types=1);

function nextVisitorNumber(): int
{
    static $visitors = 0; // reset at the start of every request
    return ++$visitors;
}

echo nextVisitorNumber(); // 1 on every page view
echo nextVisitorNumber(); // 2, same request only

go deeper

for a junior

Recall the one rule: every web request starts empty and ends with all its variables, statics and objects freed. Anything that must last goes to a database, file or session.

for a middle

Explain why a reused FPM or Apache worker does not carry script state, and contrast the CLI, where a single run is one request and statics live until exit.

for a senior

Show you think about concurrency once state moves out of the request: atomic increments or locks, and which store fits a hot counter versus per-visitor data.

for a principal

Frame share-nothing as a deliberate trade: isolation and leak resistance bought with per-request bootstrap cost, and know when a persistent runtime changes that bargain.

## The symptom A museum's website wants a visitor counter on its home page. A developer writes a function with a `static` variable, increments it on each call and prints it. On every page view the counter shows `1`. Nothing is broken: the code does exactly what PHP's execution model says it should. ## Share-nothing: what it means PHP's classic web model is called **share-nothing**. For each HTTP request the engine: 1. **starts a fresh request context**: superglobals like `$_GET` and `$_SERVER` are filled, no user variables exist yet; 2. **compiles and executes** the entry script and everything it includes; 3. **shuts the request down**: shutdown callbacks run, remaining objects are destroyed, output is flushed, and the request's memory is released. Everything a script creates lives in step 2 and dies in step 3. That includes: - ordinary local variables and **globals** (anything in `$GLOBALS`); - **static local variables** (`static $n = 0;` inside a function or method); - **static class properties** (`public static int $count`); - every object, array and string the script allocated. The next request, even one handled by the very same operating-system process, begins with none of them. ## Why a reused worker does not change this Under PHP-FPM or Apache's PHP module, a worker process handles many requests one after another. It is tempting to think a static set during request A is still there for request B. It is not: the engine tears down the user-land state between requests, and the request memory is returned to its allocator. What survives in the process is engine-level state (loaded extensions, parsed ini settings, compiled code held by an opcode cache) and never your variables. | Kind of state | Survives to the next web request? | |---|---| | local, global and `static` variables | **No** | | static class properties | **No** | | objects, including singletons | **No** | | rows in a database, files on disk | Yes, they live outside PHP | | session data | Yes, stored by the session handler and reloaded by session ID | A singleton pattern built on a static property has the same fate: it is a single instance **per request**, not per server. ## The CLI is the exception that proves the rule Run the same function in a loop from the command line and the counter climbs, because one CLI invocation is **one request** that may last minutes or hours. The static lives until the script ends. This is also why long-running CLI workers need care that web scripts do not: nothing is reset for them between jobs. ## How to keep a real count State that must outlive a request has to live **outside** it: - **A database row** incremented in one statement, such as `UPDATE counters SET views = views + 1 WHERE page = 'home'`, so two concurrent requests cannot overwrite each other. - **A file** read and rewritten under an exclusive lock, acceptable for a small site but slower and easier to get wrong. - **A session**, when the count is per visitor (for example "pages you viewed today") rather than site-wide. - **An external cache or key-value store**, when the count is hot and approximate values are acceptable. Whichever store you use, concurrency matters: many requests run at the same time in separate workers, so a read-then-write in PHP code loses updates unless the store does the increment atomically or you hold a lock. ## Why PHP was designed this way The model trades some per-request cost for robustness: - a bug that corrupts state, or a memory leak, is wiped out at the end of the request instead of accumulating; - requests cannot see each other's data by accident, which removes a class of cross-user leaks; - workers can be restarted or recycled at any time without losing anything important. The cost is that every request rebuilds its world: it loads configuration, registers autoloaders and constructs services again. Opcode caching and application-server runtimes that keep the app in memory address that cost, and they are separate topics. ## Fixes that do not work A few tempting fixes keep failing in the same way, because they all keep the value inside the request: - moving the counter from a `static` local to a **static property** or a **global**: same lifetime, same reset; - wrapping it in a **singleton**: the single instance is rebuilt on each request; - raising `memory_limit` or keeping the worker alive longer: the problem is not memory pressure but the request boundary itself. The test for any design is simple: if the value lives in a PHP variable, it is gone when the response is sent.

  • If the PHP-FPM worker process is reused, why is the static still gone?
    The worker process survives, but the engine resets each request's state: at request shutdown it destroys user variables, statics and objects and releases the request's memory. What persists in the worker is engine-level state such as loaded extensions and parsed ini settings, never script variables.
  • Would the same static counter keep counting in a PHP CLI script?
    Yes, within that run. A CLI invocation is one request, so a static lives until the script exits. Calling the function in a loop increments it; running the script again starts from zero.
  • Why is reading the count, adding one in PHP and writing it back a bug?
    Requests run concurrently in separate workers. Two may read the same value, both add one, and both write it back, losing an increment. Let the store do the increment in one statement or hold a lock around the read and write.

A hotel room cleaned after every guest: whatever the last guest left on the desk is gone for the next one, even though the room itself is reused. Anything meant to last goes to the front desk, which here is the database.

saying these in an interview costs you the question

  • A static variable keeps its value across HTTP requests on the same server.
  • PHP-FPM reuses the worker, so global variables stay set for the next request.
  • A singleton stored in a static property is shared by all users.
  • Reading the counter, adding one and saving it back is safe under load.
  • The CLI resets static variables between loop iterations just like the web.