Skip to content

Requests and responses

NAF exposes a PSR-7 server request and expects a PSR-7 response from every handler. Import helpers in each PHP file that uses them.

Read the request

use function Naf\request;

$method = request()->getMethod();
$path = request()->getUri()->getPath();
$query = request()->getQueryParams();
$authorization = request()->getHeaderLine('Authorization');
$form = request()->getParsedBody() ?? [];
$rawBody = (string) request()->getBody();

getParsedBody() exposes parsed form data. It does not automatically decode JSON. For a JSON-only endpoint, decode the body explicitly and reject invalid input:

use function Naf\{json, request};

try {
    $data = json_decode((string) request()->getBody(), true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException) {
    return json(['error' => 'Invalid JSON'], 400);
}
if (!is_array($data)) {
    return json(['error' => 'Expected an object or array'], 400);
}

Request URI and Host

Framework 0.2.12+ uses the authority sent in Host, including an explicit port, when creating the request URI. An internal SERVER_PORT does not add a port when the browser's Host omits it. A request for https://board.example.test/settings therefore keeps that origin when the container listens on port 8443. An explicit Host: board.example.test:9443 preserves port 9443.

request()->getHeader('Host') contains one value. The factory's synthesized Host is replaced by the original header. The method, path, query, cookies and other headers remain intact. This correction does not configure proxy trust or change scheme detection. Configure HTTPS and trusted proxy headers at the host boundary as before.

Combined request parameters

use function Naf\param;

$email = param()->get('email');
$city = param()->get('address.city', 'unknown');
$data = param()->all();

param() merges query parameters and parsed form data. If the parsed body is empty and Content-Type contains application/json, it decodes JSON and merges those values instead. Body values override query values at the top level. Invalid JSON contributes no values; use explicit decoding when malformed JSON must be distinguished from missing fields. A literal key containing a dot takes precedence over a nested lookup.

Use the body directly when a value from the query string should not be accepted. Handling a POST request applies this to forms.

Uploaded files

use function Naf\{json, request};

$file = request()->getUploadedFiles()['document'] ?? null;
if ($file === null || $file->getError() !== UPLOAD_ERR_OK) {
    return json(['error' => 'Upload failed'], 400);
}

// The application creates this private directory and decides which file types to accept.
$destination = BASE_PATH . '/storage/uploads/' . bin2hex(random_bytes(16));
$file->moveTo($destination);

Uploads are PSR-7 UploadedFileInterface objects. Do not use the client-supplied filename as an unchecked filesystem path. Validate type and size according to your application's needs.

Responses

use function Naf\{json, response};

return response('Hello', 200, ['Content-Type' => 'text/plain; charset=UTF-8']);

response($content = '', $status = 200, $headers = []) accepts a PSR-7-compatible body. The helper sets no default content type; state it explicitly when it matters.

use function Naf\json;

return json(['message' => 'Created'], 201);

json() serializes the value and sets Content-Type: application/json; charset=UTF-8. For a response with no body use response('', 204), not json(null, 204).

Responses without content

Framework 0.2.12+ suppresses content for HEAD, informational statuses, 204, 205 and 304 without reading the original stream. HEAD preserves an explicit Content-Length or uses a known, nonzero body size when none was supplied. Otherwise the length remains unspecified, including when the body has already been suppressed by an earlier normalization pass.

Informational statuses and 204 remove Content-Length; 205 uses zero; 304 retains supplied representation metadata. Transfer-Encoding is removed for responses without content. The emitter reapplies these rules after response-header listeners, which can change the response status or body.

HTML views

use function Naf\View\render;

return render('home', ['name' => 'World']);

This needs naf/view. render() returns a response; view() returns a string. Templates must escape untrusted values explicitly with Naf\View\s(). See Views and templates.

Redirect and refresh

use function Naf\{redirect, refresh};

return redirect('/login');       // 302
return redirect('/new-url', 301);
return refresh();               // 302 to the current URL path, without its query string

Custom responses

use Nyholm\Psr7\Response;

return new Response(202, ['Content-Type' => 'text/plain'], 'Accepted');

PSR-7 methods such as withStatus() and withHeader() return a new object. Return or assign that new response. See Events for response-header listeners.

Logging

use function Naf\log;

log()->info('Import finished', ['count' => 12]);

The default PSR-3 logger writes to logs/app.log under the application root. The directory must be writable. log() is the NAF helper; there is no logger() helper.

Redirects on the PHP development server

Framework 0.2.1 and older

Use naf/framework 0.2.2 or newer. These releases create redirects with a valid HTTP/1.1 status line, including on PHP's development server. Framework 0.2.1 used HTTP/2 here, which caused malformed responses with php -S. Update older installations using the installation guide, then remove the old protocol-normalization listener if you copied it from an earlier tutorial. No manual protocol override is needed with the supported versions.

See Serving files for stream responses and Testing for emitted status and headers.