5

PHP中的$_POST和file_get_content('php://input')

故事背景:又是一个激情四射的故事。前段时间我们在弄我们自己的开源基础组件,基础组件包括我们经常使用的一些组件,比如说aliyunMQ,aliyunSearch、request(基于guzzle)等进行封装处理。就是这个request引发了一些美好的误会。

$_POST

老规矩,查看官方文档,全世界都会骗你,但是文档不会骗你。

$HTTP_POST_VARS [已弃用]
$_POST -- $HTTP_POST_VARS [已弃用] — HTTP POST 变量
当 HTTP POST 请求的 Content-Type 是 application/x-www-form-urlencoded 或 multipart/form-data 时,会将变量以关联数组形式传入当前脚本。

$HTTP_POST_VARS 包含相同的信息,但它不是一个超全局变量。 (注意 $HTTP_POST_VARS 和 $_POST 是不同的变量,PHP 处理它们的方式不同)

一看文档就知道全部信息了,$_POST支持的request中的header中的content-type的类型只有application/x-www-form-urlencoded 或 multipart/form-data 。

当我们使用guzzle的时候它会根据你传入的params是否是数组进行判断,如果不是数组会在body中。但是如果是数组它就会按照json方式进行传递,content-type会application/json的方式当然不会被$_POST进行处理。所以,这边是没有毛病的。但是,难道因为用了这个组件就不进行这个类型处理了吗?显然不行。

php://input

php:// — 访问各个输入/输出流(I/O streams)

PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符, 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。

php://input 是个可以访问请求的原始数据的只读流。 POST 请求的情况下,最好使用 php://input 来代替 $HTTP_RAW_POST_DATA,因为它不依赖于特定的 php.ini 指令。 而且,这样的情况下 $HTTP_RAW_POST_DATA 默认没有填充, 比激活 always_populate_raw_post_data 潜在需要更少的内存。 enctype="multipart/form-data" 的时候 php://input 是无效的。

Note: 在 PHP 5.6 之前 php://input 打开的数据流只能读取一次; 数据流不支持 seek 操作。 不过,依赖于 SAPI 的实现,请求体数据被保存的时候, 它可以打开另一个 php://input 数据流并重新读取。 通常情况下,这种情况只是针对 POST 请求,而不是其他请求方式,比如 PUT 或者 PROPFIND。

这个东西说实话,就是$_POST基于这个进行封装处理了一层。它能够获取到最原始的数据,不管你是什么content-type,它都能够获取到数据。所以,当我们的post的过来的数据是原始数据的时候,比如说是application/json或者' application/x-json,text/xml, application/xml,application/x-xml这些时候,$_POST都是无法获取到数据的。此刻,我们就需要通过php://input进行获取原始数据了。但是,原始数据并不是我们想要的最终格式。因此,我们需要进行封装一层。

parseRequest

request类其实是很好写的,但是一般的类中对post方式传递参数还是老方式。因此,我改写了一般的类中获取post的参数,对于所有post请求方式的方法,针对不同的content-type进行数据获取和原始数据的解析,弄成我们想要的最终数组。
核心代码:

public static function post($key = NULL, $default = NULL)
    {
        $data = [];       if(in_array($_SERVER['CONTENT_TYPE'],self::$formats['json'])){
            $data = file_get_contents('php://input');
            $data = json_decode($data,true);
        }

        if(in_array($_SERVER['CONTENT_TYPE'],self::$formats['xml'])){
            $data = file_get_contents('php://input');
            $data = DataParser::toArray($data);
        }

        if($key==null && !empty($data)){
            return $data;
        }

        if(!empty($data)){
            return isset($data[$key]) ? $data[$key] : $default;
        }

        return static::lookup($_POST, $key, $default);
    }

在类中我们会定义几个conten-type的format数组,通过$_SERVER['CONTENT_TYPE']来进行判断处理,针对性的进行数据获取和转换。

protected static $formats = array(
        'html' => array('text/html', 'application/xhtml+xml'),
        'txt'  => array('text/plain'),
        'js'   => array('application/javascript',
            'application/x-javascript', 'text/javascript'),
        'css'  => array('text/css'),
        'json' => array('application/json', 'application/x-json'),
        'xml'  => array('text/xml', 'application/xml',
            'application/x-xml'),
        'rdf'  => array('application/rdf+xml'),
        'atom' => array('application/atom+xml'),
        'rss'  => array('application/rss+xml'),
    );

针对xml格式的数据,同时封装了数据处理类

public static function toArray($xml)
    {
        if (!$xml) {
            return false;
        }

        // 检查xml是否合法
        $xml_parser = xml_parser_create();
        if (!xml_parse($xml_parser, $xml, true)) {
            xml_parser_free($xml_parser);
            return false;
        }
        libxml_disable_entity_loader(true);

        $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);

        return $data;
    }

就是简单的将xml格式数据转换成array数据。

后话

一般$_POST在很多项目中会有使用,基本上用来使用获取参数数据。通常的请求的content-type不会出现很奇怪的,但是我觉得还是需要自己去封装一层进行数据获取的类,能够省去很多麻烦,或者说对于你们项目的一致性有很多帮助。


JVVV
593 声望9 粉丝

Go、PHP 分布式 架构