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:
- The web server (or PHP-FPM) receives the raw HTTP request and translates its method, headers, and URI into CGI-style environment variables.
- PHP’s request-startup phase copies those environment variables into the
$_SERVERsuperglobal, along with a few values PHP computes itself (likePHP_SELF). - If the request has a query string, PHP parses it into
$_GET. If the request body isapplication/x-www-form-urlencodedormultipart/form-data, PHP parses it into$_POST(and$_FILESfor uploads). Cookies from theCookieheader are parsed into$_COOKIE. - PHP then builds
$_REQUESTby iterating the sources named inrequest_order(default"GP") in order, merging each into a single array — later sources overwrite matching keys from earlier ones. - All of these superglobals are available in every scope, including inside functions and classes, without needing the
globalkeyword — 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
$_GETand$_POSTover$_REQUESTwhenever 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, orHTTP_USER_AGENT) withhtmlspecialchars()before printing it into HTML. - Never trust
$_SERVER['REMOTE_ADDR']or header-derived values likeHTTP_X_FORWARDED_FORfor security decisions without validating them against a trusted proxy configuration. - If you must use
$_REQUEST, know your server’srequest_ordersetting so you understand precedence when the same key appears in multiple sources.
Practice Exercises
- Write a script that checks
$_SERVER['REQUEST_METHOD']and prints"Hello, World"only when the method isGET; otherwise print an error message. - Write a script that reads a
searchterm from$_GETif present, otherwise from$_POST, without using$_REQUESTat all, and safely echoes it back usinghtmlspecialchars(). - Using only
$_SERVERvalues, build and print the full URL the client requested, combining the scheme (based onHTTPS),HTTP_HOST, andREQUEST_URI.
Summary
$_SERVERholds information about the server and the current HTTP request, populated from CGI-style environment variables before your script runs.$_SERVERkeys are not guaranteed to exist — always guard access withisset()or??.$_REQUESTmerges$_GET,$_POST, and sometimes$_COOKIE, based on therequest_orderphp.ini setting (default"GP").- Because
$_REQUESThides where data came from, prefer$_GET/$_POSTdirectly, especially for anything security-sensitive. - Request-derived values (from either superglobal) must be escaped with
htmlspecialchars()before being echoed into HTML to prevent XSS.
