PHP $_REQUEST and $_SERVER

Every time a browser sends a request to a PHP script, PHP automatically fills in a set of special arrays called superglobals with information about that request. Two of the most useful are $_SERVER, which describes the server environment and the incoming HTTP request, and $_REQUEST, which bundles together data submitted via GET, POST, and (sometimes) cookies. Knowing exactly what lives in each array, and where that data actually comes from, is essential for routing logic, form handling, and writing secure code.

Overview: How $_REQUEST and $_SERVER Work

$_SERVER is an associative array created before your script even starts running. The web server (Apache, Nginx via PHP-FPM, or PHP’s built-in server) hands PHP a collection of CGI-style environment variables — the request method, the URI, HTTP headers, the client’s IP address, paths on disk, and more. PHP copies these into $_SERVER at the start of the request. Because the exact set of keys depends on the server software, SAPI (mod_php vs FPM vs CLI), and even the specific request, you should never assume every key is present — always check with isset() or the null coalescing operator (??) before relying on a value.

$_REQUEST is different: it isn’t tied to the server environment at all. Instead, PHP builds it by merging $_GET, $_POST, and (depending on configuration) $_COOKIE into a single array. Which sources are merged, and in what order, is controlled by the request_order directive in php.ini (it falls back to variables_order if request_order is not set). The default in most modern PHP installations is "GP" — GET first, then POST — deliberately excluding cookies, because merging cookie data into request-driven logic has historically enabled session-fixation and parameter-pollution attacks. When the same key exists in more than one source, the later source in the order list overwrites the earlier one, so with the default "GP" order, a POST field named id will overwrite a GET parameter named id.

Because $_REQUEST‘s exact behavior depends on server configuration that you may not control, and because it deliberately hides where a value came from, it’s best treated as a convenience for quick scripts or for values you intentionally want to accept from either GET or POST — not as a general-purpose replacement for $_GET and $_POST.

Syntax

Both are ordinary associative arrays — no special syntax is needed to read from them, only awareness of what keys might exist:

<?php
// Reading a value from $_SERVER (always check isset/?? — keys are not guaranteed)
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';

// Reading a value from $_REQUEST (merged GET + POST + maybe COOKIE)
$search = $_REQUEST['q'] ?? '';
$_SERVER key Meaning
REQUEST_METHOD HTTP verb used: GET, POST, PUT, DELETE, etc.
REQUEST_URI The path and query string as sent by the client, e.g. /shop?id=3.
QUERY_STRING Just the raw query string, e.g. id=3&sort=asc.
SCRIPT_NAME / PHP_SELF Path to the currently executing script.
HTTP_HOST The Host header sent by the client.
HTTP_USER_AGENT The client’s browser/user agent string.
HTTP_REFERER The page the request was linked from, if sent.
REMOTE_ADDR The client’s IP address.
SERVER_PROTOCOL The HTTP version, e.g. HTTP/1.1.
HTTPS Set to 'on' when the request used HTTPS; often absent (not empty) otherwise.
DOCUMENT_ROOT The filesystem root the server is serving from.

Examples

Example 1: Inspecting the request with $_SERVER

<?php
echo "Request Method: " . $_SERVER['REQUEST_METHOD'] . "\n";
echo "Script Name: " . $_SERVER['SCRIPT_NAME'] . "\n";
echo "Server Software: " . $_SERVER['SERVER_SOFTWARE'] . "\n";
echo "Client IP: " . $_SERVER['REMOTE_ADDR'] . "\n";
echo "User Agent: " . $_SERVER['HTTP_USER_AGENT'] . "\n";
Output:
Request Method: GET
Script Name: /index.php
Server Software: Apache/2.4.54 (Unix)
Client IP: 203.0.113.42
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)

These values are entirely determined by the incoming request and the server configuration — running this same script on a different host, browser, or over HTTPS would print different values. That’s why $_SERVER is useful for logging, routing, and diagnostics, but its contents should never be hard-coded into your assumptions.

Example 2: Reading merged data from $_REQUEST

<?php
// Assume the request is: GET /greet.php?name=Ada&source=query-string
// (no POST body and no cookie named "name" or "source" was sent)
$name = $_REQUEST['name'] ?? 'Guest';
echo "Hello, {$name}! (via \$_REQUEST)\n";
var_dump(isset($_REQUEST['source']));
Output:
Hello, Ada! (via $_REQUEST)
bool(true)

Because the request had no POST body, $_REQUEST['name'] simply reflects the GET value. If the same request had also included a POST field named name, that POST value would win, since the default request_order is "GP" (POST is merged after, and therefore overwrites, GET).

Example 3: A realistic form handler using both superglobals

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username'] ?? '');
    $email = trim($_POST['email'] ?? '');

    if ($username === '' || $email === '') {
        echo "Error: username and email are required.\n";
    } else {
        echo "Welcome, {$username}! A confirmation will be sent to {$email}.\n";
    }
} else {
    echo "Please submit the form using POST.\n";
}
Output (assuming a POST request with username=Grace&email=grace@example.com):
Welcome, Grace! A confirmation will be sent to grace@example.com.

This is the pattern used in most real applications: $_SERVER['REQUEST_METHOD'] decides how to handle the request, while the explicit $_POST array (not $_REQUEST) supplies the actual data — because the code should only accept this data when it truly arrived as a POST.

Under the Hood: Step by Step

When a request reaches PHP, roughly this sequence happens before your script’s first line runs:

  1. The web server (or PHP-FPM) receives the raw HTTP request and translates its method, headers, and URI into CGI-style environment variables.
  2. PHP’s request-startup phase copies those environment variables into the $_SERVER superglobal, along with a few values PHP computes itself (like PHP_SELF).
  3. If the request has a query string, PHP parses it into $_GET. If the request body is application/x-www-form-urlencoded or multipart/form-data, PHP parses it into $_POST (and $_FILES for uploads). Cookies from the Cookie header are parsed into $_COOKIE.
  4. PHP then builds $_REQUEST by iterating the sources named in request_order (default "GP") in order, merging each into a single array — later sources overwrite matching keys from earlier ones.
  5. All of these superglobals are available in every scope, including inside functions and classes, without needing the global keyword — that’s what makes them “superglobal.”

Common Mistakes

Mistake 1: Using $_REQUEST for actions that should require POST

Because $_REQUEST silently accepts GET parameters too, using it to gate a destructive action means the action can be triggered just by visiting a link (or an attacker embedding it in an <img> tag) — no form submission needed:

<?php
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'delete') {
    $id = (int) $_REQUEST['id'];
    // deleteRecord($id) would run here — even from a plain GET link!
    echo "Record {$id} deleted.\n";
}

Anyone who gets a logged-in user to load /manage.php?action=delete&id=42 triggers the deletion. The fix is to require the HTTP method explicitly and read from $_POST only:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete') {
    $id = (int) ($_POST['id'] ?? 0);
    // deleteRecord($id) only runs for a genuine POST request
    echo "Record {$id} deleted.\n";
} else {
    echo "No action taken.\n";
}

Mistake 2: Assuming a $_SERVER key always exists

Many $_SERVER keys are only set under certain conditions. HTTPS, for instance, is typically only present at all when the request used TLS — comparing a missing key directly triggers an “undefined array key” warning:

<?php
$scheme = $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
echo "Scheme: {$scheme}\n";

Over a plain HTTP connection where HTTPS was never set, this emits a warning before falling back to false. Use the null coalescing operator to handle the missing case cleanly:

<?php
$scheme = (($_SERVER['HTTPS'] ?? 'off') === 'on') ? 'https' : 'http';
echo "Scheme: {$scheme}\n";

Mistake 3: Echoing $_SERVER[‘PHP_SELF’] without escaping

PHP_SELF reflects the requested script path, and on many server configurations an attacker can append extra path segments (like /form.php/"><script>...</script>) that PHP includes verbatim in that value. Printing it directly into HTML is a classic reflected XSS hole:

<?php
echo '<form action="' . $_SERVER['PHP_SELF'] . '" method="post">';
echo '<input type="text" name="q">';
echo '</form>';

Always escape request-derived values before they touch HTML output, using htmlspecialchars():

<?php
echo '<form action="' . htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES, 'UTF-8') . '" method="post">';
echo '<input type="text" name="q">';
echo '</form>';

Best Practices

  • Prefer $_GET and $_POST over $_REQUEST whenever the source of the data matters — especially for anything that changes state.
  • Never gate a destructive or state-changing action solely on $_REQUEST; check $_SERVER['REQUEST_METHOD'] and read from the appropriate array.
  • Always use isset() or ?? when reading from $_SERVER — not every key is guaranteed to exist on every SAPI or server.
  • Escape any value derived from the request (including $_SERVER['PHP_SELF'], HTTP_REFERER, or HTTP_USER_AGENT) with htmlspecialchars() before printing it into HTML.
  • Never trust $_SERVER['REMOTE_ADDR'] or header-derived values like HTTP_X_FORWARDED_FOR for security decisions without validating them against a trusted proxy configuration.
  • If you must use $_REQUEST, know your server’s request_order setting so you understand precedence when the same key appears in multiple sources.

Practice Exercises

  1. Write a script that checks $_SERVER['REQUEST_METHOD'] and prints "Hello, World" only when the method is GET; otherwise print an error message.
  2. Write a script that reads a search term from $_GET if present, otherwise from $_POST, without using $_REQUEST at all, and safely echoes it back using htmlspecialchars().
  3. Using only $_SERVER values, build and print the full URL the client requested, combining the scheme (based on HTTPS), HTTP_HOST, and REQUEST_URI.

Summary

  • $_SERVER holds information about the server and the current HTTP request, populated from CGI-style environment variables before your script runs.
  • $_SERVER keys are not guaranteed to exist — always guard access with isset() or ??.
  • $_REQUEST merges $_GET, $_POST, and sometimes $_COOKIE, based on the request_order php.ini setting (default "GP").
  • Because $_REQUEST hides where data came from, prefer $_GET/$_POST directly, especially for anything security-sensitive.
  • Request-derived values (from either superglobal) must be escaped with htmlspecialchars() before being echoed into HTML to prevent XSS.