Contents
- 1 How PHP Handles Requests
- 1.1 The Journey Begins: Web Server to PHP
- 1.2 SuperGlobals: PHP's Ready-Made Request Parsers
- 1.3 Parsing the Raw Request: Beyond SuperGlobals
- 1.4 Routing and Dispatch: From Request to Action
- 1.5 Real-World Flows: Order Processing Example
- 1.6 Edge Cases That Bite
- 1.7 Performance Tweaks for High Load
- 1.8 Wrapping the Request: Response and Teardown
How PHP Handles Requests
Hey, fellow developers. Picture this: it's 2 AM, your keyboard's glowing under the desk lamp, and that one API endpoint is throwing a 500 error. You've got a deadline tomorrow, coffee's gone cold, and you're staring at $_POST wondering why it's empty. Sound familiar? We've all been there. Requests are the heartbeat of every PHP app—GETs for quick reads, POSTs with payloads that can make or break a workflow. But how does PHP actually handle them under the hood? Let's pull back the curtain, no fluff, just the real mechanics that keep our code alive.
PHP isn't some magic box. It's a script engine that wakes up for each request, parses the chaos from the web server, and spits out HTML, JSON, or whatever your frontend craves. Whether you're building a simple form handler or a RESTful API powering an e-commerce giant, understanding this flow changes everything. It turns "why isn't this working?" into "ah, gotcha."
The Journey Begins: Web Server to PHP
Every request starts at your web server—Nginx, Apache, whatever you're running. They don't speak PHP natively. Instead, they use something like PHP-FPM (FastCGI Process Manager) to hand off the baton. Here's the dance:
- Client hits your server with an HTTP request: method (GET, POST, PUT), headers, query string, body.
- Web server checks if it's a PHP file (via
.phpextension or routing rules). - Forwards to PHP-FPM's master process.
- Master grabs an idle worker process (or spawns one if needed).
- Worker executes your script, builds the response, sends it back.
This process-per-request model is key. Each request gets its own sandboxed process—no shared memory headaches between users. But watch your pm.max_children in php-fpm.conf; too few, and requests queue up. Too many, and your server's swapping like crazy.
I remember debugging a high-traffic site where workers were dying under load. Traced it to memory leaks in a third-party lib. Lesson? Always peek at the scoreboard—PHP-FPM's status page shows busy/idle workers in real-time. Run curl http://yourserver/status (enable it in config first).
SuperGlobals: PHP's Ready-Made Request Parsers
Once PHP boots up, it populates superglobals—those magic arrays like $_GET, $_POST, $_REQUEST. No manual parsing needed for basics.
$_GET: Query string params, e.g.,/user?id=123→$_GET['id'] = '123'.$_POST: Form data from POST bodies (application/x-www-form-urlencoded or multipart/form-data).$_REQUEST: Merges GET, POST, and COOKIES (order depends onvariables_orderini setting). Handy, but risky—cookies can override GET/POST if you're not careful.$_SERVER: Everything else—REQUEST_METHOD,PATH_INFO,CONTENT_TYPE, client IP.
Quick gotcha: Modifying $_GET at runtime doesn't touch $_REQUEST, and vice versa. Always check $_SERVER['REQUEST_METHOD'] for GET/POST/PUT/DELETE logic.
Have you ever wondered why $_POST vanishes on PUT requests? PHP only auto-parses POST bodies for forms. For JSON APIs? Nope.
Parsing the Raw Request: Beyond SuperGlobals
For RESTful APIs or anything fancy, superglobals fall short. Enter raw streams.
class Request {
public $verb;
public $url_elements;
public $parameters;
public function __construct() {
$this->verb = $_SERVER['REQUEST_METHOD'];
$this->url_elements = explode('/', $_SERVER['PATH_INFO'] ?? '');
// Query params first
parse_str($_SERVER['QUERY_STRING'] ?? '', $this->parameters);
// Override with body for POST/PUT
$body = file_get_contents('php://input');
if (isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) {
$this->parameters = json_decode($body, true) ?: $this->parameters;
}
}
}
This is gold. php://input is a read-only stream of the raw POST/PUT body. No parsing by PHP—your job. For JSON, decode it. For XML, use SimpleXML. Headers? Loop getallheaders() or apache_request_headers().
Why bother? SuperGlobals choke on large payloads or non-form data. This Request class routes /users/123 cleanly: $url_elements = 'users'; $url_elements = '123';. Single index.php entry point, front-controller style. Clean, scalable.
One late night, I refactored a legacy app this way. Requests that took 200ms parsing exploded to 20ms. Feels like cheating.
Routing and Dispatch: From Request to Action
PHP doesn't route out of the box— that's your framework's job (Symfony, Laravel) or DIY. But the pattern's simple:
- Parse URL elements.
- Match verb + path to handlers.
- Inject params, run logic, respond.
// Simple router
$request = new Request();
[$resource, $id] = $request->url_elements;
switch ($resource) {
case 'users':
if ($request->verb === 'GET' && $id) {
echo json_encode(getUser($id));
} elseif ($request->verb === 'POST') {
createUser($request->parameters);
}
break;
}
Scale it with frameworks. Laravel's Route::get('/users/{id}', ...) hides the mess. Underneath? Same parsing.
Real-World Flows: Order Processing Example
Ever built an e-commerce checkout? Requests chain into workflows. Say, POST /orders with JSON payload.
Controller grabs the request, validates, orchestrates steps:
class OrderController {
public function process(Request $request) {
$order = Order::fromArray($request->parameters);
// Dynamic workflow
$steps = ['validate', 'payment'];
if ($order->customer->isPremium()) {
$steps[] = 'apply_discount';
}
$steps[] = 'ship';
foreach ($steps as $step) {
$order = $this->$step($order);
}
return json_encode(['status' => 'completed']);
}
}
This mirrors real apps. Client sends {"items": [...], "premium": true}. PHP parses, branches logic. For batches? Split into parallel workers.
I once handled 10k orders/min this way. Key: stateless workers, queue heavy lifting (Redis/RabbitMQ). PHP shines here—fast serialization, easy JSON.
Edge Cases That Bite
- File uploads:
$_FILES, but checkupload_max_filesize. - PUT/DELETE: No
$_PUT. Alwaysphp://input. - Cookies: In
$_COOKIE, but sanitize—XSS waiting. - Security:
filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT). Never trust raw input. - Large bodies: Bump
post_max_size.
Pro tip: Log requests early. error_log(json_encode($_SERVER) . "\n" . file_get_contents('php://input'));. Saved my bacon debugging prod issues.
Performance Tweaks for High Load
PHP-FPM workers process one request, die, repeat. Tune:
| Setting | Default | Tune For |
|---|---|---|
| pm | dynamic | static for steady load |
| pm.max_children | 5 | CPU cores * 2-ish |
| pm.start_servers | 2 | 25% of max_children |
| request_terminate_timeout | 0 | 30s max per request |
OpCache? Must-have. Cuts parse time 70%.
Wrapping the Request: Response and Teardown
PHP executes, outputs via echo/print. Web server wraps in HTTP response (status, headers via header()).
Script ends, worker recycles. Cycle repeats.
Frameworks add middleware: auth, CORS, rate limiting. All before your code runs.
So, next time a request flakes, trace it: server → FPM → superglobals → your router. It's predictable once you see the layers.
We've covered the guts—from raw bytes to business logic. PHP's request handling feels basic until it doesn't. Master it, and those 2 AM bugs become quiet wins, the kind that make you smile into your cold coffee.
This flow isn't just code. It's the invisible thread connecting users to your app, request by request, keeping the world spinning a little smoother.