Request

Overview

The Request class gives you a clean interface to everything the framework knows about the current incoming HTTP request: who made it, how, and what they sent. It is the single place in JiFramework for reading client IP addresses, request headers, the raw body, auth tokens, and environment variables.

  • Class: JiFramework\Core\Utilities\Request
  • Access: $app->request
  • Lifecycle: eager-loaded — instantiated immediately inside App::__construct(), before any lazy component
  • Used internally by: RateLimiter and AccessControl to resolve the client IP on every request
$app = new App();

$ip     = $app->request->getClientIp();
$method = $app->request->getRequestMethod();
$token  = $app->request->getBearerToken();

Method groups:

  • IP detectiongetClientIp()
  • Server infogetServerInfo(), getPhpVersion()
  • Request inspectiongetRequestHeaders(), getRequestMethod(), isHttps(), isAjax(), isCli(), getBody(), getBearerToken()
  • Environment variablesgetEnv()

getClientIp()

getClientIp(): string

Return the client IP address for the current request. This is the single authoritative IP resolution point used across the entire framework — by the rate limiter, access control, and any direct call from application code.

Returns: string — a valid IP address (IPv4 or IPv6), or an empty string when REMOTE_ADDR is absent or not a valid IP.

Resolution order:

  1. If debug_ip is set and app_mode = development — return debug_ip (dev override for localhost testing).
  2. If REMOTE_ADDR is in trusted_proxies — walk X-Forwarded-For right-to-left and return the first non-proxy IP.
  3. Otherwise — return REMOTE_ADDR directly (the default and most common path).

Security note: REMOTE_ADDR is the actual TCP connection IP and cannot be forged by the client. X-Forwarded-For is a client-controlled header and is only read when REMOTE_ADDR matches a trusted proxy you configured. If you do not run behind a proxy, leave trusted_proxies empty — no configuration required.

$ip = $app->request->getClientIp();

// Log, pass to access control, store in audit trail, etc.
$app->logger->info('Incoming request from {ip}', ['ip' => $ip]);

IP Detection — Configuration

Two jiconfig.php keys control how getClientIp() resolves the client IP. Both default to off — no configuration is needed for a standard single-server setup.

trusted_proxies

(array) IP addresses of trusted reverse proxies — load balancers, CDNs, or an Nginx front-end sitting in front of PHP-FPM. When REMOTE_ADDR matches one of these addresses, the real client IP is read from X-Forwarded-For (walking right-to-left to skip any intermediate proxy hops). Default: []

debug_ip

(string|null) Override the detected client IP for local development. Useful for testing rate limiting and access control on localhost where REMOTE_ADDR is always 127.0.0.1. Only honoured when app_mode = development — in production mode it is silently ignored and a warning is written to the log. Default: null

// Running behind Nginx, a load balancer, or a CDN:
'trusted_proxies' => ['127.0.0.1', '10.0.0.1'],

// Testing IP-based features on localhost:
'app_mode' => 'development',
'debug_ip'  => '203.0.113.42',   // RFC 5737 test range — clearly fictional

Multiple proxy hops

If your traffic passes through more than one proxy, add all of their IPs to trusted_proxies. The framework walks X-Forwarded-For from right to left and stops at the first IP that is not in the trusted list — that is the real client.

// CDN (10.0.0.3) → Load balancer (10.0.0.2) → PHP server
'trusted_proxies' => ['10.0.0.2', '10.0.0.3'],
// X-Forwarded-For: 203.0.113.42, 10.0.0.3, 10.0.0.2
// → resolved IP: 203.0.113.42

getServerInfo()

getServerInfo(): array

Return a snapshot of server-level environment data. This covers information about the web server itself, not the individual HTTP request. For request-specific data use getRequestMethod(), $app->url->path(), and $app->url->queryParam().

Returns: array with the following keys (empty string when the underlying $_SERVER key is absent):

  • server_software(string) Web server identifier, e.g. Apache/2.4.57 or nginx/1.25.3.
  • server_protocol(string) HTTP protocol version, e.g. HTTP/1.1 or HTTP/2.
  • document_root(string) Absolute path to the web server document root.
  • remote_addr(string) Raw REMOTE_ADDR (the TCP connection IP, before any proxy resolution). Use getClientIp() when you want the resolved client IP.
$info = $app->request->getServerInfo();

echo $info['server_software'];  // "Apache/2.4.57 (Debian)"
echo $info['server_protocol'];  // "HTTP/1.1"
echo $info['document_root'];    // "/var/www/html"
echo $info['remote_addr'];      // "127.0.0.1"  (raw TCP connection IP)

getPhpVersion()

getPhpVersion(): string

Return the current PHP version string. Equivalent to calling phpversion() directly.

Returns: string — the PHP version in x.y.z format, e.g. "8.2.12".

$ver = $app->request->getPhpVersion();
echo $ver; // "8.2.12"

// Enforce a minimum version at runtime
if (version_compare($ver, '8.1.0', '<')) {
    $app->abort(500, 'PHP 8.1 or higher is required.');
}

getRequestHeaders()

getRequestHeaders(): array

Return all HTTP headers sent with the current request as an associative array. Header names are title-cased (e.g. Content-Type, Accept-Language).

Returns: array<string, string> — header name → value pairs.

Uses getallheaders() when available (Apache, PHP-FPM). Falls back to iterating $_SERVER HTTP_* keys on other SAPIs so it works everywhere.

$headers = $app->request->getRequestHeaders();

// Case-insensitive lookup
$normalized = array_change_key_case($headers, CASE_LOWER);

$contentType = $normalized['content-type'] ?? '';
$accept      = $normalized['accept']       ?? '';

// Check for a custom header
if (isset($normalized['x-api-version'])) {
    $apiVersion = $normalized['x-api-version'];
}

getRequestMethod()

getRequestMethod(): string

Return the HTTP method for the current request in uppercase. Returns "GET" when $_SERVER['REQUEST_METHOD'] is absent.

Returns: string — e.g. "GET", "POST", "PUT", "DELETE", "PATCH".

$method = $app->request->getRequestMethod();

switch ($method) {
    case 'GET':
        // return the resource
        break;
    case 'POST':
        // create
        break;
    case 'PUT':
    case 'PATCH':
        // update
        break;
    case 'DELETE':
        // delete
        break;
    default:
        $app->abort(405, 'Method Not Allowed');
}

Request Flags

Three lightweight boolean methods that inspect the nature of the current request.

isHttps(): bool

Returns true when the current request was made over HTTPS. Handles all common setups:

  • Direct SSL/TLS: $_SERVER['HTTPS'] === 'on'
  • Port-based detection: $_SERVER['SERVER_PORT'] == 443
  • Reverse proxy / CDN: X-Forwarded-Proto: https (case-insensitive)
if (!$app->request->isHttps()) {
    // Redirect HTTP to HTTPS
    $app->redirect('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
}
isAjax(): bool

Returns true when the request carries the X-Requested-With: XMLHttpRequest header. jQuery, Axios, and most HTTP client libraries set this automatically. The check is case-insensitive. Note that the native Fetch API does not set this header by default.

if ($app->request->isAjax()) {
    $app->json(200, ['status' => 'ok', 'data' => $result]);
} else {
    // Render full HTML page
    include 'views/result.php';
}
isCli(): bool

Returns true when PHP is running on the command line (PHP_SAPI === 'cli'). Useful for scripts that run both as a web request and as a cron job / CLI tool.

if ($app->request->isCli()) {
    // Running as: php run-job.php
    echo 'Running in CLI mode' . PHP_EOL;
} else {
    // Running as a web request — HTML output is expected
    header('Content-Type: text/html');
}

getBody()

getBody(): string

Return the raw request body read from php://input. This is the standard way to read JSON payloads, XML, or any other non-form-encoded data sent in a POST, PUT, or PATCH request.

Returns: string — the raw body, or an empty string when there is no body (e.g. a GET request).

Note: For standard HTML form submissions (Content-Type: application/x-www-form-urlencoded or multipart/form-data), PHP already parses the data into $_POST and $_FILES. Use getBody() for JSON APIs or custom content types where PHP does not parse the body automatically.

// JSON API endpoint
if ($app->request->getRequestMethod() === 'POST') {
    $raw  = $app->request->getBody();
    $data = json_decode($raw, true);

    if (!is_array($data)) {
        $app->json(400, ['error' => 'Invalid JSON body']);
    }

    $name  = $data['name']  ?? '';
    $email = $data['email'] ?? '';
    // ...
}

getBearerToken()

getBearerToken(): string|null

Extract the token value from an Authorization: Bearer <token> header. Returns null when the header is absent, empty, or uses a different authentication scheme (e.g. Basic, Digest).

Returns: string|null — the bare token string, or null.

The "Bearer" scheme keyword check is case-insensitive. The token itself is returned exactly as received, with no decoding or validation applied.

$token = $app->request->getBearerToken();

if ($token === null) {
    $app->json(401, ['error' => 'Authorization token required']);
}

// Verify the token (e.g. a JWT or an opaque API key)
$payload = $app->auth->verifyApiToken($token);

if ($payload === false) {
    $app->json(401, ['error' => 'Invalid or expired token']);
}

Tip: On Apache, PHP may not populate $_SERVER['HTTP_AUTHORIZATION'] by default. Add the following line to your .htaccess to pass the header through:

SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

getEnv()

getEnv(string $key, mixed $default = null): mixed

Read a server or process environment variable. Checks $_ENV first, then falls back to getenv() so it works regardless of the variables_order setting in php.ini. Returns $default when the variable is not set.

  • $key(string) The environment variable name.
  • $default(mixed) Value to return when the variable is not set. Default: null.

Returns: mixed — the variable value, or $default.

// Read an environment variable set in .env / server config
$dbHost   = $app->request->getEnv('DB_HOST', 'localhost');
$apiKey   = $app->request->getEnv('STRIPE_API_KEY');
$appEnv   = $app->request->getEnv('APP_ENV', 'production');

if ($apiKey === null) {
    $app->logger->error('STRIPE_API_KEY environment variable is not set');
    $app->abort(500, 'Payment service is not configured');
}

Tip: Store sensitive credentials (API keys, database passwords) in environment variables or a secrets manager rather than in config files. This prevents them from being accidentally committed to version control.

Examples

JSON API endpoint that requires Bearer auth

// api/orders.php
$req = $app->request;

// Only accept POST requests
if ($req->getRequestMethod() !== 'POST') {
    $app->json(405, ['error' => 'Method Not Allowed']);
}

// Require a Bearer token
$token = $req->getBearerToken();
if ($token === null || !$app->auth->verifyApiToken($token)) {
    $app->json(401, ['error' => 'Unauthorized']);
}

// Decode JSON body
$data = json_decode($req->getBody(), true);
if (!is_array($data)) {
    $app->json(400, ['error' => 'Invalid JSON']);
}

// Process the order...
$app->json(201, ['status' => 'created', 'id' => $newOrderId]);

Unified HTML / AJAX response

// pages/search.php
$results = $app->db->table('products')->where('name', 'LIKE', '%' . $q . '%')->get();

if ($app->request->isAjax()) {
    $app->json(200, ['results' => $results]);
} else {
    include 'views/search.php'; // full page render
}

HTTP → HTTPS redirect

// index.php — force HTTPS in production
if (!$app->request->isHttps() && Config::$appMode === 'production') {
    $app->redirect('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
}

Audit log with full request context

$req = $app->request;

$app->logger->info('Incoming request', [
    'ip'     => $req->getClientIp(),
    'method' => $req->getRequestMethod(),
    'https'  => $req->isHttps() ? 'yes' : 'no',
    'ajax'   => $req->isAjax()  ? 'yes' : 'no',
    'php'    => $req->getPhpVersion(),
]);