How a Request Becomes a Page

A PHP worker has two lifecycles, one inside the other. At process start, module startup (MINIT) reads php.ini, loads extensions and attaches to OPcache's shared memory. Then, for every request, request startup (RINIT) fills $_GET, $_SERVER and the other superglobals; the script is compiled to opcodes, or taken ready-made from OPcache, and executed; and request shutdown (RSHUTDOWN) runs destructors, flushes output and frees every variable the request created.

The two lifecycles of a PHP worker: once per process, once per request
The two lifecycles of a PHP worker: once per process, once per request

The next listing makes both lifecycles visible:

counter.php: variables die with the request, compiled code does notPHP
<?php
static $hits = 0;
$hits++;
$ms = (microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000;
$op = opcache_get_status(false);
printf("pid %d  hits %d  opcache hits %d  %.2f ms\n",
    getmypid(), $hits, $op['opcache_statistics']['hits'] ?? 0, $ms);

Requested five times through Apache 129 with mod_php, it prints:

Output of 1
pid 27533  hits 1  opcache hits 0  0.92 ms
pid 27534  hits 1  opcache hits 1  0.63 ms
pid 27530  hits 1  opcache hits 2  0.22 ms
pid 27531  hits 1  opcache hits 3  0.19 ms
pid 27532  hits 1  opcache hits 4  0.18 ms

$hits is 1 every time because RSHUTDOWN destroyed it; under PHP-FPM, one worker answered two of five requests and printed 1 both times. The OPcache counter climbs because compiled scripts live in shared memory: the first request paid for compilation, later ones ran in about 0.2 ms. A file modified in the last two seconds is not cached yet (opcache.file_update_protection). Performance and OPcache tunes OPcache.