# The Request Lifecycle, PHP-FPM and Front Controllers — PHP

Source: https://www.skillbyai.com/en/php/w-request

> Explain how a request reaches PHP and how applications route it.

## From NGINX to your code

In production, a web server such as **NGINX** receives the HTTP request, serves static files itself, and forwards PHP requests over FastCGI to **PHP-FPM**, which keeps a pool of worker processes ready. Each worker runs one request at a time: it bootstraps your application, handles the request and resets state. Request data is available through **superglobals**: `$_GET` (query string), `$_POST` (form bodies), `$_SERVER` (headers, method, URI), `$_COOKIE`, `$_FILES` and `$_SESSION`; JSON bodies are read from `php://input`. Modern applications use a **front controller**: every request goes to one `public/index.php`, which loads the autoloader, builds the application and passes the request to a **router** that maps method and path to a controller. Frameworks wrap superglobals in request and response **objects** (PSR-7 or framework-specific), which are easier to test. The web server's document root should be the `public/` directory only, so source code, `.env` files and `vendor/` are never directly reachable.

## NGINX, PHP-FPM and the front controller

NGINX serves static files and forwards dynamic requests to PHP-FPM workers running index.php.

![A web server box sending arrows to a static file stack and to a pool of worker circles, each worker pointing to a single entry file icon.](assets/figures/php/section-5-map.svg) — Figure 5.1 — Request flow from NGINX to PHP-FPM workers and the front controller.

## A minimal front controller with routing

One entry point maps method and path to handlers.

```php
<?php
// public/index.php
declare(strict_types=1);

require __DIR__ . '/../vendor/autoload.php';

$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

$routes = [
    'GET /health'  => fn (): array => ['status' => 'ok'],
    'GET /orders'  => fn (): array => (new Shop\Orders\OrderQuery())->recent(),
    'POST /orders' => function (): array {
        $body = json_decode(file_get_contents('php://input'), true, flags: JSON_THROW_ON_ERROR);
        return (new Shop\Orders\PlaceOrder())->handle($body);
    },
];

$handler = $routes["{$method} {$path}"] ?? null;

header('Content-Type: application/json');
if ($handler === null) {
    http_response_code(404);
    echo json_encode(['error' => 'Not found']);
    exit;
}
echo json_encode($handler(), JSON_THROW_ON_ERROR);

// nginx: root /var/www/shop/public;  try_files $uri /index.php?$query_string;
```

## Document root must be public/

If the web root is the project directory, a request for `/.env` or `/vendor/composer/installed.json` may download secrets and dependency details. Point the server at `public/` only.

**Quiz:** In a typical production setup, which component runs PHP code for NGINX?

- [x] PHP-FPM via FastCGI
- [ ] Apache Tomcat
- [ ] Node.js
- [ ] The browser

*Answer:* PHP-FPM via FastCGI. NGINX forwards PHP requests to PHP-FPM worker processes over FastCGI.
