How to check HTTP request in PHP?

The Hypertext Transfer Protocol (HTTP) is designed to enable communications between clients and servers.

HTTP works as a request-response protocol between a client and server.

Example: A client (browser) sends an HTTP request to the server; then the server returns a response to the client. The response contains status information about the request and may also contain the requested content.


  • GET
  • POST
  • PUT
  • HEAD
  • DELETE
  • PATCH
  • OPTIONS
  • CONNECT
  • TRACE

The two most common HTTP methods are: GET and POST.


The GET Method

GET is used to request data from a specified resource.

Note that the query string (name/value pairs) is sent in the URL of a GET request:

/test/demo_form.php?name1=value1&name2=value2

Some notes on GET requests:

  • GET requests can be cached
  • GET requests remain in the browser history
  • GET requests can be bookmarked
  • GET requests should never be used when dealing with sensitive data
  • GET requests have length restrictions
  • GET requests are only used to request data (not modify)

The POST Method

POST is used to send data to a server to create/update a resource.

The data sent to the server with POST is stored in the request body of the HTTP request:

POST /test/demo_form.php HTTP/1.1
Host: w3schools.com

name1=value1&name2=value2

Some notes on POST requests:

  • POST requests are never cached
  • POST requests do not remain in the browser history
  • POST requests cannot be bookmarked
  • POST requests have no restrictions on data length

Compare GET vs. POST

The following table compares the two HTTP methods: GET and POST.

GETPOSTBACK button/ReloadHarmlessData will be re-submitted (the browser should alert the user that the data are about to be re-submitted)BookmarkedCan be bookmarkedCannot be bookmarkedCachedCan be cachedNot cachedEncoding typeapplication/x-www-form-urlencodedapplication/x-www-form-urlencoded or multipart/form-data. Use multipart encoding for binary dataHistoryParameters remain in browser historyParameters are not saved in browser historyRestrictions on data lengthYes, when sending data, the GET method adds the data to the URL; and the length of a URL is limited (maximum URL length is 2048 characters)No restrictionsRestrictions on data typeOnly ASCII characters allowedNo restrictions. Binary data is also allowedSecurityGET is less secure compared to POST because data sent is part of the URL

Never use GET when sending passwords or other sensitive information!

POST is a little safer than GET because the parameters are not stored in browser history or in web server logsVisibilityData is visible to everyone in the URLData is not displayed in the URL



The PUT Method

PUT is used to send data to a server to create/update a resource.

The difference between POST and PUT is that PUT requests are idempotent. That is, calling the same PUT request multiple times will always produce the same result. In contrast, calling a POST request repeatedly have side effects of creating the same resource multiple times.


The HEAD Method

HEAD is almost identical to GET, but without the response body.

In other words, if GET /users returns a list of users, then HEAD /users will make the same request but will not return the list of users.

HEAD requests are useful for checking what a GET request will return before actually making a GET request - like before downloading a large file or response body.


The DELETE Method

The DELETE method deletes the specified resource.


The PATCH Method

The PATCH method is used to apply partial modifications to a resource.


The OPTIONS Method

The OPTIONS method describes the communication options for the target resource.


The CONNECT Method

The CONNECT method is used to start a two-way communications (a tunnel) with the requested resource.


The TRACE Method

The TRACE method is used to perform a message loop-back test that tests the path for the target resource (useful for debugging purposes).

Requests are issued as described in the HTTP protocol. But the way of accessing the data they contain is of course PHP specific. It is then interesting to analyze the adequacy of the interface offered by PHP and the descriptions allowed by the protocol.

Description of an HTTP request

Before sending a request, the server has access to basic information about the client: IP address and possibly SSL context. This phase takes place before the launch of PHP and is passed to it via environment variables (

<?php

$key_name = 'foo.bar';
$query_str = rawurlencode($key_name);
parse_str($query_str, $array_result);

// KO in this example, foo.bar becomes foo_bar
echo isset($array_result[$key_name])
    ? 'OK: PHP and HTML key addressing is identical'
    : 'KO: key contains specials characters for PHP';

?>
1 and
<?php

$key_name = 'foo.bar';
$query_str = rawurlencode($key_name);
parse_str($query_str, $array_result);

// KO in this example, foo.bar becomes foo_bar
echo isset($array_result[$key_name])
    ? 'OK: PHP and HTML key addressing is identical'
    : 'KO: key contains specials characters for PHP';

?>
2 eg.).

The request itself, in its primary structure, contains three subsections:

  1. The first line contains the HTTP method (GET, POST, etc..), the URL and the used protocol version (HTTP/1.1). This line is the only one the web server may need to decide on handing over to PHP. This information is available via
    <?php
    
    $key_name = 'foo.bar';
    $query_str = rawurlencode($key_name);
    parse_str($query_str, $array_result);
    
    // KO in this example, foo.bar becomes foo_bar
    echo isset($array_result[$key_name])
        ? 'OK: PHP and HTML key addressing is identical'
        : 'KO: key contains specials characters for PHP';
    
    ?>
    3,
    <?php
    
    function parseQuery($query)
    {
        $fields = array();
    
        foreach (explode('&', $query) as $q)
        {
            $q = explode('=', $q, 2);
            if ('' === $q[0]) continue;
            $q = array_map('urldecode', $q);
            $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
        }
    
        return $fields;
    }
    
    ?>
    0 and
    <?php
    
    $key_name = 'foo.bar';
    $query_str = rawurlencode($key_name);
    parse_str($query_str, $array_result);
    
    // KO in this example, foo.bar becomes foo_bar
    echo isset($array_result[$key_name])
        ? 'OK: PHP and HTML key addressing is identical'
        : 'KO: key contains specials characters for PHP';
    
    ?>
    5 respectively.
  2. The following lines, as they are not empty, make a list of keys and values: the headers.
  3. The blank line that ends the headers is followed by the body of the request, whose content should be interpreted according to the Content-Type header. The body of the request is typically empty for GET requests and containing the values ​​of form fields for POST requests.
Description and limitations of the PHP interface

Until the first line of the request, all information is available if the server send it to PHP via environment variables.

HTTP headers

For each key/value header pair, PHP creates an

<?php

$key_name = 'foo.bar';
$query_str = rawurlencode($key_name);
parse_str($query_str, $array_result);

// KO in this example, foo.bar becomes foo_bar
echo isset($array_result[$key_name])
    ? 'OK: PHP and HTML key addressing is identical'
    : 'KO: key contains specials characters for PHP';

?>
6 index containing the raw value. This index is created by putting the original key in capital then altering certain non-alphanumeric characters. To the extent that the name of the header is case insensitive and special characters do not carry specific information, this transformation does not hide any useful information. As $_SERVER is an array, it can not contain two identical index. However, nothing prevents the same key to be present several times in the headers. As this situation may not occur (except in certain targeted attacks) this limitation has no practical consequence. Note also that two headers may be different but identical for PHP after transformation as described above.

The

<?php

$key_name = 'foo.bar';
$query_str = rawurlencode($key_name);
parse_str($query_str, $array_result);

// KO in this example, foo.bar becomes foo_bar
echo isset($array_result[$key_name])
    ? 'OK: PHP and HTML key addressing is identical'
    : 'KO: key contains specials characters for PHP';

?>
8 function is an other mean to fetch HTTP request headers, which is only available when using Apache SAPI (or FastCGI since PHP5.4). It returns key => value pairs, so duplicate headers still collide.

Cookies and URL parameters

The Cookie header and the URL parameters contain themselves key/value pairs that are available for more comfort with

<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
1 and $_GET respectively. The algorithm to go from raw string to an array is exposed by
<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
1, which allows for easy observation of the operation:

As with headers, some non-alphanumeric characters of the keys are replaced with an underscore or deleted, the case is preserved, but the unicity of keys still applies. As is common here to need multiple values ​​of the same key, PHP allows you to create arrays from scalar data sources using brackets in the name of the keys. This trick allows to circumvent the unicity limitation by combining all the values ​​of the same key in an array. This syntax can also appoint key to create an array of arrays, in the hope that this makes life easier for developers. For example:

<?php

foo=A&foo=B       => $_GET['foo'] = 'B';
foo[]=A&foo[]=B   => $_GET['foo'] = array(0 => 'A', 1 => 'B');
foo[]=A&foo=B     => $_GET['foo'] = 'B';
foo=A&foo[]=B     => $_GET['foo'] = array(0 => 'B');
foo[bar][]=A      => $_GET['foo'] = array('bar' => array(0 => 'A'));
foo[]=A&foo[0]=B  => $_GET['foo'] = array(0 => 'B');

?>

If this technique removes the initial restriction, it is based on a semantic confusion: in the above example,

<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
2,
<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
3 and
<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
4 should not collide. This is reflected for instance if you want to access to an HTML form element via
<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
5 in the browser: the full name, with the brackets if any, is required. However, PHP side, these variants collide. We can consider here that PHP requires the developer to adopt special agreements to circumvent the internal limitations that the protocol does not have.

However, since the raw information can be found in $_SERVER, it is possible to create another interface that does not suffer from this defect.

Request body

The body of the request is to be interpreted according to the Content-Type header. HTML defines two possible values: multipart/form-data and application/x-www-form-urlencoded, also interpreted natively by PHP. Other contents are possible, eg JSON or XML for some web-services (server to server and AJAX).

The type multipart/form-data is opaque to PHP developers: only

<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
3 and
<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
4 are available, without any access to raw data. Other types of content are accessible via the
<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
9 stream. This point remains to be verified by testing the various SAPI (Apache module, FastCGI, CGI, etc.). Since PHP 5.4, request body processing can be disabled by setting off the
<?php

function test_query_key_name($key_name, &$multivalues_capable = null)
{
    $multivalues_capable = false;
    $a = rawurlencode($key_name);
    parse_str($a . '&' . $a, $a);
    $canonic_name = key($a);
    if (null === $canonic_name) return false;
    $a = $a[$canonic_name];

    while (is_array($a))
    {
        if (2 === count($a))
        {
            $canonic_name .= '[]';
            $multivalues_capable = true;
        }
        else
        {
            $canonic_name .= '[' . key($a) . ']';
        }

        $a = end($a);
    }

    return $canonic_name === $key_name;
}

?>
0 ini setting.

How these arrays are filled is identical to that described above (specific characters altered, brackets in the name of the keys, collisions). They therefore suffer the same defects.

Bracketed syntax: an unnecessary complexity

The magic of bracketed syntax creates unnecessary complexity for the PHP developer:

  • Where the data source is a simple keys/values list, PHP has a recursive tree structure thus more difficult to handle.
  • It introduces a difference between PHP and HTML addressing.
  • The literal syntax is anyway needed in the generated HTML.

It is thus an abstraction that doesn't abstract anything, because it adds to the literal syntax without improving or replacing it. It would be equally effective to access data via the literal name of the keys, using prefixes or suffixes where necessary to contextualize the variables (functional equivalence between

<?php

function test_query_key_name($key_name, &$multivalues_capable = null)
{
    $multivalues_capable = false;
    $a = rawurlencode($key_name);
    parse_str($a . '&' . $a, $a);
    $canonic_name = key($a);
    if (null === $canonic_name) return false;
    $a = $a[$canonic_name];

    while (is_array($a))
    {
        if (2 === count($a))
        {
            $canonic_name .= '[]';
            $multivalues_capable = true;
        }
        else
        {
            $canonic_name .= '[' . key($a) . ']';
        }

        $a = end($a);
    }

    return $canonic_name === $key_name;
}

?>
1 and
<?php

function test_query_key_name($key_name, &$multivalues_capable = null)
{
    $multivalues_capable = false;
    $a = rawurlencode($key_name);
    parse_str($a . '&' . $a, $a);
    $canonic_name = key($a);
    if (null === $canonic_name) return false;
    $a = $a[$canonic_name];

    while (is_array($a))
    {
        if (2 === count($a))
        {
            $canonic_name .= '[]';
            $multivalues_capable = true;
        }
        else
        {
            $canonic_name .= '[' . key($a) . ']';
        }

        $a = end($a);
    }

    return $canonic_name === $key_name;
}

?>
2 for example). The brackets are still needed for multi-valued keys.

Improving native PHP interface

Problems and (false) solutions

To summarize the previous section, the interface provided by PHP suffers from the following defects:

  1. <?php
    
    function test_query_key_name($key_name, &$multivalues_capable = null)
    {
        $multivalues_capable = false;
        $a = rawurlencode($key_name);
        parse_str($a . '&' . $a, $a);
        $canonic_name = key($a);
        if (null === $canonic_name) return false;
        $a = $a[$canonic_name];
    
        while (is_array($a))
        {
            if (2 === count($a))
            {
                $canonic_name .= '[]';
                $multivalues_capable = true;
            }
            else
            {
                $canonic_name .= '[' . key($a) . ']';
            }
    
            $a = end($a);
        }
    
        return $canonic_name === $key_name;
    }
    
    ?>
    3:
  2. mixing different sources contexts, should never be used.
  3. $_SERVER:
  4. alteration of particular characters in headers names,
  5. name collision for repeated headers,
  6. web server dependency to get request context via environment variables.
  7. $_GET,
    <?php
    
    function parseQuery($query)
    {
        $fields = array();
    
        foreach (explode('&', $query) as $q)
        {
            $q = explode('=', $q, 2);
            if ('' === $q[0]) continue;
            $q = array_map('urldecode', $q);
            $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
        }
    
        return $fields;
    }
    
    ?>
    1,
    <?php
    
    function parseQuery($query)
    {
        $fields = array();
    
        foreach (explode('&', $query) as $q)
        {
            $q = explode('=', $q, 2);
            if ('' === $q[0]) continue;
            $q = array_map('urldecode', $q);
            $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
        }
    
        return $fields;
    }
    
    ?>
    3,
    <?php
    
    function parseQuery($query)
    {
        $fields = array();
    
        foreach (explode('&', $query) as $q)
        {
            $q = explode('=', $q, 2);
            if ('' === $q[0]) continue;
            $q = array_map('urldecode', $q);
            $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
        }
    
        return $fields;
    }
    
    ?>
    4:
  8. alteration of particular characters in keys names,
  9. name collision for multi-valued keys,
  10. non-semantic collision created by the bracketed syntax and by items 2.1 and 3.1,
  11. complexity introduced by the bracketed syntax.
  12. Access to raw data:
  13. no method is referenced to access the raw HTTP headers, but
    <?php
    
    $key_name = 'foo.bar';
    $query_str = rawurlencode($key_name);
    parse_str($query_str, $array_result);
    
    // KO in this example, foo.bar becomes foo_bar
    echo isset($array_result[$key_name])
        ? 'OK: PHP and HTML key addressing is identical'
        : 'KO: key contains specials characters for PHP';
    
    ?>
    8 can help when available,
  14. the input to $_GET and
    <?php
    
    function parseQuery($query)
    {
        $fields = array();
    
        foreach (explode('&', $query) as $q)
        {
            $q = explode('=', $q, 2);
            if ('' === $q[0]) continue;
            $q = array_map('urldecode', $q);
            $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
        }
    
        return $fields;
    }
    
    ?>
    1 are in $_SERVER,
  15. <?php
    
    $key_name = 'foo';
    $query_str = rawurlencode($key_name);
    $query_str .= '&' . $query_str; // Here is the trick
    parse_str($query_str, $array_result);
    
    // KO in this example, foo can't address multiple values  
    echo is_array($array_result[$key_name])
        ? 'OK: key name can address multiple values'
        : 'KO: PHP restricts this key name to a single value';
    
    ?>
    9 should allow access to the body of the request, but only when the
    <?php
    
    function test_query_key_name($key_name, &$multivalues_capable = null)
    {
        $multivalues_capable = false;
        $a = rawurlencode($key_name);
        parse_str($a . '&' . $a, $a);
        $canonic_name = key($a);
        if (null === $canonic_name) return false;
        $a = $a[$canonic_name];
    
        while (is_array($a))
        {
            if (2 === count($a))
            {
                $canonic_name .= '[]';
                $multivalues_capable = true;
            }
            else
            {
                $canonic_name .= '[' . key($a) . ']';
            }
    
            $a = end($a);
        }
    
        return $canonic_name === $key_name;
    }
    
    ?>
    0 ini setting is set to off or for contents other than multipart/form-data.

Destroying

<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
5 as soon as possible to avoid any temptation to use it solves item 1.1. Anyway, its portability is limited by php.ini.

Item 4.1 makes it impossible to fix defects 2.1 and 2.2. Point 2.3 is inherent in how PHP works.

Point 4.3 requires writing and parsing the request body in PHP. When a large file is transferred, it is embarrassing to monopolize the server with such a process. In addition, this is unlikely to be portable because it requires changing the configuration of the web server. This therefore seems not viable as a general solution.

If 4.3 is not working, altered data in

<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
3 and
<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
4 are the only one available to access input data. For the sake of consistency, despite 4.2, rebuilding $_GET and
<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
1 from their raw data does not seem a good idea. However, here is an implementation that can analyze a raw string like
<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
2:

<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>

Finally, to avoid breaking the current interface, documentation and customs that go with $_GET

<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
1,
<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
3 or
<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
4, their original state as defined by PHP should be kept.

The desired solution should allow to mitigate defects 3.1, 3.2, 3.3 and 3.4 using $_GET,

<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
1,
<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
3 and
<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
4 as data sources, without modifying them. As a corollary, access to data in PHP and HTML should be done using exactly the same keys.

Key normalization

Item 3.1 is not a very restrictive limitation. Indeed, the keys are already known by the application that needs them. They carry no information other than the labeling of form data. The real problem is that 3.1 introduces a potential difference between the way of naming fields in HTML, and how to access them in PHP. For example, an HTML field named

<?php

use Patchwork\HttpQueryField as Field;

$field = new Field('foo[bar][]');
$values = $field->getValues($_GET);
$single_value = end($values);
// or
$values = Field::getNew('foo[bar][]')->getValues($_GET);
// or
$single_value = end(Field::getNew('foo[bar][]')->getValues($_GET));

?>
9 will be available via $_SERVER0. The solution however is quite simple: just avoid all keys names that introduce a difference between HTML and PHP. This solution also solves 3.3.

Bracketed syntax apart, PHP processing of keys is more complicated than a search and replace. For example, a $_SERVER1 is replaced by a $_SERVER2 only if it is not preceded by a $_SERVER3 and so on. Fortunately,

<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
1 allows us to test and especially to reproduce this behavior in PHP. To verify that a key name is acceptable to PHP, just use
<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
1:

<?php

$key_name = 'foo.bar';
$query_str = rawurlencode($key_name);
parse_str($query_str, $array_result);

// KO in this example, foo.bar becomes foo_bar
echo isset($array_result[$key_name])
    ? 'OK: PHP and HTML key addressing is identical'
    : 'KO: key contains specials characters for PHP';

?>

The collision 3.2 for multi-valued keys can only be circumvented by using empty brackets in keys names. Again,

<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
1 allows us to access this behavior while ignoring implementation details. To test whether a particular key can be used to manage multiple values, consider the following code:

<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>

If the principle of this code is a step in the right direction, it does not work because it does not take into account the specifics of the bracketed syntax.

To account for the syntax, a key name as to be rebuild from the $_SERVER7 structure as given by

<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
1 and then compared to $_SERVER9. Here is such a function:

<?php

function test_query_key_name($key_name, &$multivalues_capable = null)
{
    $multivalues_capable = false;
    $a = rawurlencode($key_name);
    parse_str($a . '&' . $a, $a);
    $canonic_name = key($a);
    if (null === $canonic_name) return false;
    $a = $a[$canonic_name];

    while (is_array($a))
    {
        if (2 === count($a))
        {
            $canonic_name .= '[]';
            $multivalues_capable = true;
        }
        else
        {
            $canonic_name .= '[' . key($a) . ']';
        }

        $a = end($a);
    }

    return $canonic_name === $key_name;
}

?>

This function can test if a key name is acceptable in PHP, if it can accept multiple values ​​and supports the bracketed syntax (it also illustrates very well the unnecessary complexity introduced by this syntax). If each key name used by an application satisfies this test, then 3.1, 3.2 and 3.3 are solved.

Access by literal keys

The point 3.4 is the last to be resolved: accessing data in $_GET,

<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
1,
<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
3 or
<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
4 (or more generally, an array built by
<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
1) using the literal version of the keys.

The function we seek to create is therefore at least two input parameters: the looked up literal $_SERVER9 and an HTTP_*6 collection built by

<?php

$key_name = 'foo';
$query_str = rawurlencode($key_name);
$query_str .= '&' . $query_str; // Here is the trick
parse_str($query_str, $array_result);

// KO in this example, foo can't address multiple values  
echo is_array($array_result[$key_name])
    ? 'OK: key name can address multiple values'
    : 'KO: PHP restricts this key name to a single value';

?>
1. It returns a list of values ​​in HTTP_*6 that match $_SERVER9. In the case where $_SERVER9 would not be allowed by PHP (see above), the function could return $_GET1 or cause an exception:

<?php

function get_values_from_parse_str($key_name, $parse_str_array)
{
    if (!test_query_key_name($key_name)) return false;
    $values = array();
    // [...] extract values matching $key_name from $parse_str_array
    return $values ;
}

?>

Thus, instead of using $_GET2 to access URL parameter $_GET3, we could use $_GET4.

The structure of

<?php

function parseQuery($query)
{
    $fields = array();

    foreach (explode('&', $query) as $q)
    {
        $q = explode('=', $q, 2);
        if ('' === $q[0]) continue;
        $q = array_map('urldecode', $q);
        $fields[$q[0]][] = isset($q[1]) ? $q[1] : '';
    }

    return $fields;
}

?>
4 is a bit more complex, but the same logic applies.

Conclusion

PHP offers comprehensive autoglobals to access external data sent with each request. These variables do not expose all the possibilities allowed by the HTTP protocol, but a controlled use can in practice minimize the impact of this limitation.

Two problems are particularly troublesome:

  1. lack of access to multi-valued keys ​​without using a special syntax,
  2. complexity of the magic bracketed syntax.

Until PHP natively provides another interface freed from these problems, a different interface in user space can circumvent them.

How to get the HTTP request in PHP?

5 Ways to Make HTTP Requests in PHP.
PHP's HTTP/s stream wrapper..
PHP's cURL extension..
GuzzleHttp..
Httpful..
Symfony's HTTP client..

How to get HTTP request header in PHP?

Receiving the request header, the web server will send an HTTP response header back to the client. Read any request header: It can be achieved by using getallheaders() function. Example 2: It can be achieved by using apache_request_headers() function.

How to check post request in PHP?

PHP POST request php $name = $_POST['name']; if ($name == null) { $name = 'guest'; } $message = $_POST['message']; if ($message == null) { $message = 'hello there'; } echo "$name says: $message"; The example retrieves the name and message parameters from the $_POST variable. We start the server.

How to check server request method in PHP?

To check the request method you may use the $_SERVER['REQUEST_METHOD'] variable, the $_SERVER is a PHP superglobal that is available to you at any time, even inside functions and classes. To use the REQUEST_METHOD variable you could just echo its contents, but it is probably more useful in a switch or if statement.