14

In PHP I would like to be able to access PUT and DELETE vars globally similar to how GET and POST vars are accessed globally. I originally considered adding the data to $_PUT and $_DELETE respectively in the global namespace, but then I realized that the data for each request is stored in the message body so there's no way for there to be more than one dataset from a POST, PUT, or DELETE request.

Are there any side-effects of overwriting the $_POST variable?

i.e. str_parse( file_get_contents( 'php://input' ), $_POST );

Am I being silly, or is there a better way to access PUT and DELETE data?


Edit to clarify my thinking:

I am very well aware of the source of the data in $_POST, in fact i mentioned it earlier in my question. If a HTTP POST request is sent to the server the data is stored in php://input. If a HTTP PUT or DELETE request is sent to the server, the data is stored in the exact same place, meaning that $_POST will be empty (as no data was POSTed despite data being available.

A GET request, on the other hand, is passed via the query string. This allows simultaneous passing of $_POST and $_GET variables. It is not possible to simultaneously pass POST and PUT or DELETE variables.

If I overwrite $_POST from php://input on PUT and or DELETE requests, there is no data loss.

The alternative of adding:

global $_PUT;
global $_DELETE;

to the beginning of functions seems silly, as I'll only be able to use one at a time anyway.

My first question, which is the one I really want answered, is about what side-effects or issues exist in overwriting $_POST. I can't possibly be the first person to try something as silly as:

$_POST['foo'] = 'bar';

I'm just concerned that if I do anything similar that it might not be preserved across scopes.

Community
  • 1
  • 1
zzzzBov
  • 174,988
  • 54
  • 320
  • 367
  • 2
    I have a car. taping some plastic over the windows doesn't turn it into a boat. Overwriting superglobals with data from some other HTTP method will just lead to long term pain, especially if you plan to make this code modular and/or share it with others. $_POST is for POST data, $_GET is for GET data. Don't mix it all up. – Marc B Jun 07 '11 at 19:00
  • @Marc B, ok, but what if I overwrite `$_POST` with an object that acts as a wrapper for the `$_POST` data, implementing the array interfaces (ArrayAccess, Countable, etc)? Additional functionality can be provided without modifying how a developer interacts with the `$_POST` superglobal. – zzzzBov Jun 14 '11 at 16:56
  • That'd be fine, since it's just recreating whatever's there. But subverting POST to actually be HEAD would break everything else in the system. – Marc B Jun 14 '11 at 17:06
  • You can modify `_POST` or `_GET` without worrying too much about PHP itself, is just a variable. The main concern of others is that you may not know at a later time your `POST` or `GET` data is "magically" modified. I've modified `_POST` in the past when I'm not able to modify some methods that don't rely on parameters but superglobals – Ast Derek Jan 27 '12 at 17:12
  • If only they'd called the request body $_BODY instead of $_POST, the answer would be obvious.. – Greg Jan 11 '13 at 21:35

4 Answers4

15

You'll see this called "bad practice" all over the internet, but if you really get in to why it is "bad practice", well, the answers get fuzzy. The most concrete reason is the "hit by a bus" scenario so often bandied about - what if the project gets handed off to a new developer?

Hand wringing aside (you can leave comments, after all), there really isn't a compelling reason not to do it like this, but again, there isn't a compelling reason to do it, either. Why not put the values in a $_SESSION key if you want them global? Or make a global variable? Or make a static class to access the PUT/DELETE values through? With all the other optional approaches, I think that overwriting $_POST, while it won't make your server explode, is the most likely to cause you a headache down the road.

I threw this little static class together, you'll want to test this out before relying on it. Use:

//To check if this is a rest request:
Rest::isRest();

//To get a parameter from PUT
$put_var = Rest::put('keyname', false);

//To get a parameter from DELETE
$dele_var = Rest::delete('keyname', false);

 class Rest {
    static private $put = false;
    static private $delete = false;
    static private $is_rest = false;
    function __construct() {
        self::$is_rest = true;
        switch ($_SERVER['REQUEST_METHOD']) {
            case 'PUT':
                parse_str(self::getVars(), self::$put);
                break;
            case 'DELETE':
                parse_str(self::getVars(), self::$delete);
                break;
            default:
                self::$is_rest = false;
        }
    }
    private static function getVars() {
        if (strlen(trim($vars = file_get_contents('php://input'))) === 0)
            $vars = false;
        return $vars;
    }
    public static function delete($key=false, $default=false) {
        if (self::$is_rest !== true)
            return $default;
        if (is_array(self::$delete) && array_key_exists($key, self::$delete))
            return self::$delete[$key];
        return $default;
    }
    public static function put($key=false, $default=false) {
        if (self::$is_rest !== true)
            return $default;
        if (is_array(self::$put) && array_key_exists($key, self::$put))
            return self::$put[$key];
        return $default;
    }
    public static function isRest() {
        return self::$is_rest;
    }
}
Chris Baker
  • 49,926
  • 12
  • 96
  • 115
  • +1 but IMO I'd avoid making everything static. I've also used DI where the contents of `php://input` is passed in the constructor. This makes it much easier to test with (along with not using statics/globals). – Paul DelRe Jun 07 '11 at 20:28
  • 1
    The purpose of the static is to avoid having to instantiate it multiple times across isolated segments of code. If it weren't static, each instance would do a full read with `file_get_contents`, which can be heavy. The use of static methods allows the code to read once. Plus, with a static class, it becomes globally available without polluting the global scope (much like the superglobal the OP is trying to emulate). – Chris Baker Jun 07 '11 at 20:37
  • Agreed w/ reading `php://input` multiple times would be bad. Additionally, as I learned first hand, there are some cases where `php://input` is cleared on the first read! – Paul DelRe Jun 07 '11 at 20:50
  • @Chris, my fallback is to use a statically invokable class (read: function with OOP encapsulation and other features) so that I could just call `GET('foo')`, `POST('foo')`, `PUT('foo')` or `DELETE('foo')` where necessary. It would also open me up to static methods like `GET::filter('foo', $callback)`. Given all the negativity I've received around overriding `$_POST` this is probably going to be my course of action. – zzzzBov Jun 07 '11 at 21:08
  • That isn't a bad idea. With the new magic method `__callStatic`, you could easily implement these, but I don't know about the first syntax you mention (`GET('var')`) - I don't think you'd be able to use a static class like that. Maybe with `__invoke`, but I don't think that will work with a static method... never tied. – Chris Baker Jun 07 '11 at 21:13
  • @Chris, I made a mistake before, to use a classname as a function (at least as of 5.3), you just need to make a function with the same name as the class. I believe checking `isset($this)` in `__invoke` worked in an earlier version of php (5.0-5.2?), but I can't seem to find any code where I've used that particular "feature". – zzzzBov Jun 14 '11 at 16:52
  • Not having static methods, does not imply that constructor has to be called many times, or that a file has to be read more than once. – qbolec Mar 28 '14 at 21:12
  • @qbolec In the intervening years since I wrote this, I have learned better, and would not use static methods either. Might update the answer... meh. – Chris Baker Oct 30 '14 at 22:30
4

Leave Post and Get as it is. it shouldn't be modified as it's for reading purposes only. Create $_PUT and $_DELETE globals:

//  globals
$_DELETE = array ();
$_PUT = array ();

switch ( $_SERVER['REQUEST_METHOD'] ) {
    case !strcasecmp($_SERVER['REQUEST_METHOD'],'DELETE'):
        parse_str( file_get_contents( 'php://input' ), $_DELETE );
        break;

    case !strcasecmp($_SERVER['REQUEST_METHOD'],'PUT'):
        parse_str( file_get_contents( 'php://input' ), $_PUT );
        break;
}

Not tested but you should get the idea. I was in the search for a rest framework myself some weeks ago and decided to go with python. Recess (http://www.recessframework.org/) sounds promising though

jameshfisher
  • 34,029
  • 31
  • 121
  • 167
Romeo M.
  • 3,218
  • 8
  • 35
  • 40
  • But why would you want to copy the entire contents of the input stream into another variable? What if it's a 500MB file (or larger)? What is the point of this? – AJ. Jun 07 '11 at 19:10
  • 1
    It's just a pure example of setting put and delete info. I could go 100 lines with validations and stuff but I would go away from the subject. Just trying to keep it simple and to the subject. – Romeo M. Jun 07 '11 at 19:14
  • @AJ, if the file is too large, `file_get_contents` returns false nicely, it's not hard to implement a check to (fail silently | trigger error | throw exception | use fallback input). – zzzzBov Jun 08 '11 at 18:50
3

You shouldn't modify $_POST directly as this represents values coming from the client. Consider it read-only, and do any modifications in user-defined variable.

As a follow up regarding accessing PUT and DELETE data, currently there is no superglobal built in to PHP to access this data directly. As the data is file data, which can be rather large, the usefulness and efficiency of reading the entire file contents in a typical assignment statement $variable = $_PUT['file']; is questionable. Instead, it should be read in chunks. As such, the usage is consistent with reading from any other file input resource.

More on PUT here:

http://php.net/manual/en/features.file-upload.put-method.php

AJ.
  • 27,586
  • 18
  • 84
  • 94
-1

If you create a "request" object, then regardless whether the request comes over HTTP, command line, or through an HTML5 web socket, you will have a uniform way to access request data. You can then make the request object accessible in the global scope, or pass it as an argument to the required functions or methods.

Ideally you would store data that is independent of the request in static or global variables, e.g. settings that are "static" regardless of the request, and data specific to the request in a local variable or object, that could be used by your business logic. If you had a web socket server, for example, it would be easier to handle multiple request objects in a single PHP process. Here is an example that might help:

 $headers = getallheaders();
 $query = parse_str($_SERVER['QUERY_STRING']);
 $data = file_get_contents('php://input');

 if(strpos($headers['Content-Type'],'application/x-www-form-urlencoded') !== false)
 {
      $data = parse_str($data);
 }
 elseif(strpos($headers['Content-Type'],'application/json') !== false)
 {
      $data = json_decode($data);
 }
 elseif(strpos($headers['Content-Type'],'application/soap+xml') !== false)
 {
      $data = // parse soap
 }
 elseif(strpos($headers['Content-Type'],'application/xml') !== false)
 {
      $data = // parse xml
 }
 // else ...


 $request = new Request($_SERVER['REQUEST_METHOD'],$data,$query);

 // example business logic

    $method = $request->get_request_method();

    $obj = new BlogPost();
    if($method == 'GET')
    {
        $obj->id($request->get_query('id'));
        $obj->load();
    }
    elseif($method == 'PUT')
    {
        $obj->id($request->get_query('id'));
        $obj->title($request->get_data('title'));
        $obj->body($request->get_data('body'));
        $obj->save();
    }
    elseif($method == 'POST')
    {
        $obj->title($request->get_data('title'));
        $obj->body($request->get_data('body'));
        $obj->save();
    }
    elseif($method == 'DELETE')
    {
        $obj->id($request->get_query('id'));
        $obj->wipe();
    }

Regardless of whether it is a PUT, POST, PATCH, or DELETE, there is only one body of data in the HTTP request, so your application does not need a complex $request object. The request object can make your controller (if you are using MVC) very simple.

Frank Forte
  • 2,031
  • 20
  • 19
  • I appreciate your enthusiasm and the method that you describe in your answer, however the question I asked was very clearly "Are there any side-effects of overwriting the $_POST variable?". Your answer doesn't appear to answer this question, so I have to flag this as not an answer. – zzzzBov Jan 26 '15 at 21:38