Skip to Content
Course content

81: Understanding the PHP Request Lifecycle

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've been writing PHP for a while now, and you're probably comfortable with the "code in, HTML out" flow. But have you ever stopped to wonder what's actually happening in the milliseconds between you hitting 'Enter' in your browser and the page appearing? To most, it's a black box. To an engineer, that black box is the request lifecycle, and if you don't understand it, you'll eventually hit a wall when debugging memory leaks or trying to optimize performance.

Let's stop guessing and actually track a request. I'm going to build a tiny "lifecycle tracker" to see where PHP starts and, more importantly, where it dies.

Hunting for the Start Line

I'll start with a simple script, profile.php, that pretends to fetch a user from a database. I want to know exactly when PHP takes over from the web server (like Nginx or Apache).

<?php
file_put_contents('lifecycle.log', "Request started at: " . microtime(true) . "\n", FILE_APPEND);

$user = ['name' => 'Devin', 'role' => 'Engineer'];
echo "<h1>User Profile: {$user['name']}</h1>";

file_put_contents('lifecycle.log', "Request ended at: " . microtime(true) . "\n", FILE_APPEND);
?>

If I run this and check lifecycle.log, I see two timestamps. It looks straightforward: the server gets the request, hands it to PHP, PHP runs the code from top to bottom, and then it's done. But wait—what happens if the script crashes? Or what if I use exit() halfway through? I suspect there's a cleanup phase I'm missing.

Testing the 'Share Nothing' Assumption

One of the weirdest things for people coming from Node.js or Python is PHP's "share nothing" architecture. In those languages, you can have a global variable that stays in memory across different users' requests. I remember trying this in PHP years ago and being baffled when it didn't work. Let's prove it right now.

I'll add a global counter to my script to see if it increments every time I refresh the page:

<?php
global $requestCount;
$requestCount++; 

echo "This is request number: " . $requestCount;
?>

I refresh the page. It says "1". I refresh again. It still says "1". No matter how many times I hit F5, it never hits 2. Why? Because the PHP interpreter is wiped clean after every single request. The process (or the worker thread in PHP-FPM) essentially resets. This is a superpower for stability—one user's crashed script can't poison the memory for everyone else—but it's a pain when you actually want to persist data, which is why we rely so heavily on sessions and databases.

Catching the Exit

Now, back to that "cleanup" phase. If I call die() or exit(), my second file_put_contents call never happens. But I have a feeling PHP is still doing something behind the scenes before the process actually terminates. To prove this, I'll use register_shutdown_function().

This function tells PHP: "No matter how this script ends—whether it finishes naturally or crashes—run this one last bit of code."

<?php
register_shutdown_function(function() {
    file_put_contents('lifecycle.log', "Shutdown function executed at: " . microtime(true) . "\n", FILE_APPEND);
});

file_put_contents('lifecycle.log', "Main script started at: " . microtime(true) . "\n", FILE_APPEND);

// Let's simulate a hard stop
exit("Stopping early!");
?>

When I run this, the page displays "Stopping early!", but when I check the log, I see the "Main script started" entry and the "Shutdown function executed" entry. This confirms the lifecycle: Request $\rightarrow$ SAPI (Server API) $\rightarrow$ Compilation $\rightarrow$ Execution $\rightarrow$ Shutdown $\rightarrow$ Termination.

The "SAPI" part is just a fancy way of saying the bridge between your web server and PHP. Whether you're using mod_php or PHP-FPM, the SAPI is what tells PHP, "Hey, here is a request from a browser, here are the GET/POST variables; go do your thing." Once that shutdown function finishes, the memory is freed, and the process is ready for the next stranger who hits your URL.




📋 Practical Task

Building a Request Execution Timer

To put this lifecycle knowledge into practice, you are going to build a diagnostic script that measures the exact duration of a PHP request, including the time spent in the shutdown phase.

Requirements:

  • Create a script called timer.php.
  • At the very top of the script, capture the start time using microtime(true).
  • Store this start time in a variable that is accessible to a shutdown function (hint: you may need to use a global variable or a closure).
  • Register a shutdown function that captures the end time, calculates the difference between the start and end, and logs the total execution time (in milliseconds) to a file named performance.log.
  • Inside the main body of your script, simulate a "heavy" process using usleep(50000) (50 milliseconds) and then call exit("Process Interrupted").
  • Verify that despite the exit() call, the total execution time is still correctly logged to the file.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.