【问题标题】:Overwriting $_POST for PUT or DELETE requests为 PUT 或 DELETE 请求覆盖 ​​$_POST
【发布时间】:2011-06-07 18:39:50
【问题描述】:

在 PHP 中,我希望能够全局访问 PUTDELETE 变量,类似于全局访问 GETPOST 变量的方式。我最初考虑将数据分别添加到全局命名空间中的$_PUT$_DELETE,但后来我意识到每个请求的数据都存储在消息正文中,因此无法从一个POSTPUTDELETE 请求。

覆盖$_POST 变量是否有任何副作用?

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

我是不是很傻,还是有更好的方法来访问 PUTDELETE 数据?


编辑以澄清我的想法:

我非常清楚$_POST 中的数据来源,实际上我在前面的问题中提到过。如果向服务器发送 HTTP POST 请求,则数据存储在 php://input 中。如果向服务器发送 HTTP PUT 或 DELETE 请求,则数据将存储在完全相同的位置,这意味着 $_POST 将为空(因为尽管数据可用,但没有数据是 POSTed

另一方面,GET 请求是通过查询字符串传递的。这允许同时传递$_POST$_GET 变量。 不可能同时传递POSTPUT or DELETE 变量。

如果我在PUT 和/或DELETE 请求上从php://input 覆盖$_POST,则不会丢失数据。

添加的替代方法:

global $_PUT;
global $_DELETE;

函数的开头似乎很愚蠢,因为我一次只能使用一个无论如何

我真正想要回答的第一个问题是关于覆盖$_POST 时存在哪些副作用或问题。我不可能是第一个尝试这样愚蠢的事情的人:

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

我只是担心如果我做任何类似的事情,它可能不会跨范围保留。

【问题讨论】:

  • 我有车。在窗户上贴一些塑料不会把它变成一条船。用来自其他 HTTP 方法的数据覆盖超全局变量只会导致长期的痛苦,特别是如果您计划将此代码模块化和/或与其他人共享它。 $_POST 用于 POST 数据,$_GET 用于 GET 数据。不要把它们混在一起。
  • @Marc B,好的,但是如果我用一个充当$_POST 数据包装器的对象覆盖$_POST,实现数组接口(ArrayAccess、Countable 等)怎么办?无需修改开发人员与$_POST 超全局交互的方式即可提供附加功能。
  • 那很好,因为它只是重新创建那里的任何东西。但是将 POST 颠覆为真正的 HEAD 会破坏系统中的所有其他内容。
  • 你可以修改_POST_GET不用太担心PHP本身,只是一个变量。其他人主要担心的是,您以后可能不知道您的POSTGET 数据被“神奇地”修改了。我过去修改了_POST,当时我无法修改一些不依赖参数但超全局的方法
  • 如果他们将请求正文称为 $_BODY 而不是 $_POST,答案将是显而易见的......

标签: php rest


【解决方案1】:

您会在整个互联网上看到这种被称为“不良做法”的说法,但如果您真的了解为什么这是“不良做法”,那么答案就会变得模糊。最具体的原因是“被公共汽车撞”的场景经常被人津津乐道——如果项目被移交给新的开发人员怎么办?

手忙脚乱(毕竟你可以离开 cmets),确实没有令人信服的理由 这样做,但同样,没有令人信服的理由 to 也这样做。如果您希望它们是全局的,为什么不将值放在 $_SESSION 键中?还是做一个全局变量?或者创建一个静态类来访问 PUT/DELETE 值?对于所有其他可选方法,我认为覆盖 $_POST 虽然不会使您的服务器爆炸,但最有可能在未来让您头疼。

我将这个小静态类放在一起,您需要在依赖它之前对其进行测试。使用:

//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;
    }
}

【讨论】:

  • +1 但 IMO 我会避免将所有内容都设为静态。我还使用了 DI,其中 php://input 的内容在构造函数中传递。这使得测试变得更容易(以及不使用静态/全局)。
  • 静态的目的是避免在隔离的代码段中多次实例化它。如果它不是静态的,每个实例都会使用file_get_contents 进行完整读取,这可能很重。静态方法的使用允许代码读取一次。另外,使用静态类,它可以在不污染全局范围的情况下全局可用(很像 OP 试图模拟的超全局)。
  • 同意多次阅读php://input 会很糟糕。此外,据我亲身了解,在某些情况下php://input 在第一次读取时被清除!
  • @Chris,我的后备方案是使用静态可调用类(阅读:具有 OOP 封装和其他功能的函数),这样我就可以调用 GET('foo')POST('foo')PUT('foo') 或 @ 987654331@ 必要时。它也会让我接触到像GET::filter('foo', $callback) 这样的静态方法。考虑到我在覆盖$_POST 时收到的所有消极情绪,这可能是我的行动方针。
  • 这不是一个坏主意。使用新的魔术方法__callStatic,您可以轻松实现这些,但我不知道您提到的第一个语法(GET('var')) - 我认为您无法使用这样的静态类.也许使用__invoke,但我认为这不适用于静态方法......永远不会绑定。
【解决方案2】:

保持原样发布并获取。它不应该被修改,因为它只是为了阅读目的。创建 $_PUT 和 $_DELETE 全局变量:

//  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;
}

未经测试,但您应该明白这一点。几周前,我自己在寻找一个休息框架,并决定使用 python。不过,Recess (http://www.recessframework.org/) 听起来很有希望

【讨论】:

  • 但是为什么要将输入流的全部内容复制到另一个变量中呢?如果是 500MB(或更大)的文件怎么办?这有什么意义?
  • 这只是一个设置放置和删除信息的纯粹示例。我可以使用 100 行进行验证和其他东西,但我会离开这个主题。只是尽量保持简单和主题。
  • @AJ,如果文件太大,file_get_contents 很好地返回 false,实现检查并不难(静默失败 | 触发错误 | 抛出异常 | 使用回退输入)。跨度>
【解决方案3】:

您不应直接修改$_POST,因为这表示来自客户端的值。将其视为只读,并对用户定义的变量进行任何修改。

作为关于访问 PUT 和 DELETE 数据的后续措施,目前 PHP 没有内置超全局来直接访问这些数据。由于数据是文件数据,可能相当大,在典型的赋值语句$variable = $_PUT['file'];中读取整个文件内容的有用性和效率值得怀疑。相反,它应该分块读取。因此,用法与从任何其他文件输入资源中读取是一致的。

更多关于 PUT 的信息:

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

【讨论】:

  • 你没有回复他is there a better way to access PUT and DELETE data? 部分
  • @yes123 - 去吧......总是有多个答案的空间;)
  • @AJ:其实我很喜欢它,因为我也想知道XDDDD
【解决方案4】:

如果您创建一个“请求”对象,那么无论请求是通过 HTTP、命令行还是通过 HTML5 Web 套接字来的,您都将有一个统一的方式来访问请求数据。然后,您可以使请求对象在全局范围内可访问,或者将其作为参数传递给所需的函数或方法。

理想情况下,您可以将独立于请求的数据存储在静态或全局变量中,例如无论请求如何,设置都是“静态”的,以及本地变量或对象中特定于请求的数据,可以由您的业务逻辑使用。例如,如果您有一个 Web 套接字服务器,那么在单个 PHP 进程中处理多个请求对象会更容易。这是一个可能有帮助的示例:

 $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();
    }

无论是 PUT、POST、PATCH 还是 DELETE,HTTP 请求中只有一个数据体,因此您的应用程序不需要复杂的 $request 对象。请求对象可以使您的控制器(如果您使用 MVC)非常简单。

【讨论】:

  • 感谢您的热情和您在回答中描述的方法,但是我问的问题非常清楚“覆盖 $_POST 变量是否有任何副作用?”。您的回答似乎没有回答这个问题,所以我必须将此标记为不是答案。
猜你喜欢
  • 2019-07-26
  • 1970-01-01
  • 2017-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-10
  • 2019-04-21
相关资源
最近更新 更多