# Request lifecycle

What happens from the moment the browser knocks on the site to the moment HTML is returned.

## Step by step

1. **Web server.** `nginx` sits in front and serves static files (css/js/img) directly. PHP requests are proxied to Apache/PHP-FPM, which — via `.htaccess` — routes everything to `index.php` (front) or `admin.php` (admin).

2. **Entry point.** `index.php` checks for `install.lock` (otherwise it redirects to the installer) and includes `core/bootstrap.php`.

3. **Bootstrap + Kernel.** `core/bootstrap.php` creates the `Kernel`, which:
   - loads the `Autoloader` (PSR-4);
   - builds the DI `Container`;
   - reads `Config` from `.env` and `config/*.php`;
   - registers services: `Connection` (DB, lazy), `Engine` (templates), `Router`, `Logger`, `Locale`, `Translator`, `Auth`, `Options` and others;
   - starts the session (unless CLI).

4. **Locale.** The active language is detected (`Locale::detect()`: `?lang` → cookie → `Accept-Language` → default) and set on the `Translator`.

5. **Routes.** `bootstrap.php` registers public and admin routes. Finally the active plugins are loaded (`PluginManager::load()`) — their `plugin.php` files add their own routes, hooks and tags.

6. **Dispatch.** `Kernel::handle($request)` calls `Router::dispatch()`. The router matches the path against route patterns and finds the handler.

7. **Middleware.** If the route has middleware (e.g. `AuthMiddleware` for `/admin/*`), the request passes through them.

8. **Controller.** The router resolves the controller from the container (with dependency auto-injection) and calls the `(Request $request, array $params)` method. The controller returns a `Response`.

9. **Response.** `Kernel::send($response)` sets the status, headers and outputs the body.

## The router

A route is registered like this:

```php
$router->get('/news/{slug:[^/]+}', NewsController::class . '@publicShow');
$router->post('/api/leads/submit', LeadsController::class . '@submit');

$router->group(['prefix' => '/admin', 'middleware' => [AuthMiddleware::class]], function (Router $r) {
    $r->get('/users', UsersController::class . '@index');
});
```

- `{slug}` — a parameter (default `[^/]+`); you can supply your own regex: `{id:\d+}`.
- Groups add a shared prefix and middleware.
- A `Class@method` handler is lazy: the class is created via the container only when the route matches.

## Container and auto-injection

The `Container` builds objects by reflection: it inspects the constructor argument types and supplies the needed services. So a controller simply declares its dependencies:

```php
final class MyController
{
    public function __construct(
        private readonly Engine $engine,
        private readonly Options $options,
    ) {}

    public function show(Request $request, array $params): Response
    {
        $this->engine->setTheme((string) $this->options->get('theme', 'default'));
        return Response::html($this->engine->render('my-template', ['x' => 1]));
    }
}
```

Registrations: `bind()` — a new instance per request, `singleton()` — one per application, `instance()` — a pre-built object.

## Response

The response object is built via factories:

```php
Response::html($html);            // text/html
Response::json($data);            // application/json
Response::redirect('/url');       // 302
Response::notFound();             // 404
Response::text('ok');             // text/plain
```

## Error handling

In `production` (`APP_ENV=production`) the stack trace is not sent out — the user sees "500 Server Error" while details are written to `storage/logs`. In dev mode the full trace is shown. This behaviour is enforced: even with `APP_DEBUG=true` accidentally left on in production, the trace won't leak.
