场景:
最近在搭建PHP服务的时候,遇到一个问题,选用框架是YII,通过POSTMAN提交数据,提交方式选择POST
,其中Body主要有以下三个选项
1、form-data
2、x-www-form-urlencoded
3、raw
当选择1、form-data时,提交能正常接收到POST数据,但是选择raw+JSON(application/json)时,POST接收数据为空,
这个问题出现的原因是什么呢?需要我们从YII实现的原理说起
1.通过 curl 发送 json 格式的数据,譬如代码:
<?phpfunction http_post_json($url, $jsonStr){ $ch = curl_init(); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonStr); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json; charset=utf-8', 'Content-Length: ' . strlen($jsonStr) ) ); $response = curl_exec($ch); //$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $response;}$api_url = 'http://fecshop.appapi.fancyecommerce.com/44.php';$post_data = [ 'username' => 'terry', 'password' => 'terry4321'];
然后在接收端,使用$_POST接收,发现打印是空的
原因是,PHP 默认只识别 application/x-www.form-urlencoded 标准的数据类型,因此接收不到,只能通过
//第一种方法$post = $GLOBALS['HTTP_RAW_POST_DATA'];//第二种方法$post = file_get_contents("php://input");
来接收
2.如果我们在Yii2框架内,想通过
$username = Yii::$app->request->post('username');$password = Yii::$app->request->post('password');
这种方式获取第一部分使用 curl json 方式传递的 post 参数,我们发现是不行的,我们需要设置 yii2 request component
'request' => [ 'class' => 'yii\web\Request', 'parsers' => [ 'application/json' => 'yii\web\JsonParser', ], ],
然后我们通过
$username = Yii::$app->request->post('username');$password = Yii::$app->request->post('password');
发现是可以取值的了,然后如果你打印 $_POST,会发现这里依旧没有值,这是为什么呢?
下面我们通过代码顺藤摸瓜的查一下Yii2的源代码:
1.打开 yiiwebRequest 找到post()方法:
public function post($name = null, $defaultValue = null) { if ($name === null) { return $this->getBodyParams(); } return $this->getBodyParam($name, $defaultValue); }
发现值是由 $this->getBodyParam($name, $defaultValue) 给予
然后找到这个方法,代码如下:
/** * Returns the request parameters given in the request body. * * Request parameters are determined using the parsers configured in [[parsers]] property. * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()` * to parse the [[rawBody|request body]]. * @return array the request parameters given in the request body. * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]]. * @see getMethod() * @see getBodyParam() * @see setBodyParams() */ public function getBodyParams() { if ($this->_bodyParams === null) { if (isset($_POST[$this->methodParam])) { $this->_bodyParams = $_POST; unset($this->_bodyParams[$this->methodParam]); return $this->_bodyParams; } $rawContentType = $this->getContentType(); if (($pos = strpos($rawContentType, ';')) !== false) { // e.g. application/json; charset=UTF-8 $contentType = substr($rawContentType, 0, $pos); } else { $contentType = $rawContentType; } if (isset($this->parsers[$contentType])) { $parser = Yii::createObject($this->parsers[$contentType]); if (!($parser instanceof RequestParserInterface)) { throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface."); } $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType); } elseif (isset($this->parsers['*'])) { $parser = Yii::createObject($this->parsers['*']); if (!($parser instanceof RequestParserInterface)) { throw new InvalidConfigException("The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface."); } $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType); } elseif ($this->getMethod() === 'POST') { // PHP has already parsed the body so we have all params in $_POST $this->_bodyParams = $_POST; } else { $this->_bodyParams = []; mb_parse_str($this->getRawBody(), $this->_bodyParams); } } return $this->_bodyParams; }
打印 $rawContentType = $this->getContentType(); 这个变量,发现他的值为: application/json , 然后查看函数getContentType()
public function getContentType() { if (isset($_SERVER['CONTENT_TYPE'])) { return $_SERVER['CONTENT_TYPE']; } if (isset($_SERVER['HTTP_CONTENT_TYPE'])) { //fix bug https://bugs.php.net/bug.php?id=66606 return $_SERVER['HTTP_CONTENT_TYPE']; } return null; }
也就是 当我们发送json格式的curl请求, $_SERVER['CONTENT_TYPE'] 的值为 application/json
2.重新回到上面的函数 getBodyParams(),他会继续执行下面的代码:
if (isset($this->parsers[$contentType])) { $parser = Yii::createObject($this->parsers[$contentType]); if (!($parser instanceof RequestParserInterface)) { throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface."); } $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);}
$parser 就是根据我们下面的request component配置中的 parsers中得到'yiiwebJsonParser',进而通过容器生成出来的
'request' => [ 'class' => 'yii\web\Request', 'enableCookieValidation' => false, 'parsers' => [ 'application/json' => 'yii\web\JsonParser', ],],
因此返回值就是 $parser->parse($this->getRawBody(), $rawContentType); 返回的,
3.首先我们查看传递的第一个参数是函数 $this->getRawBody(),代码如下:
public function getRawBody(){ if ($this->_rawBody === null) { $this->_rawBody = file_get_contents('php://input'); } return $this->_rawBody;}
通过这个函数,回到前面我们说的,可以通过
//第一种方法$post = $GLOBALS['HTTP_RAW_POST_DATA'];//第二种方法$post = file_get_contents("php://input");
这两种方式获取curl json传递的json数据,yii2使用的是第二种。
然后我们打开 yiiwebJsonParser
/** * Parses a HTTP request body. * @param string $rawBody the raw HTTP request body. * @param string $contentType the content type specified for the request body. * @return array parameters parsed from the request body * @throws BadRequestHttpException if the body contains invalid json and [[throwException]] is `true`. */public function parse($rawBody, $contentType){ try { $parameters = Json::decode($rawBody, $this->asArray); return $parameters === null ? [] : $parameters; } catch (InvalidParamException $e) { if ($this->throwException) { throw new BadRequestHttpException('Invalid JSON data in request body: ' . $e->getMessage()); } return []; }}
可以看到这里是将传递的json转换成数组,然后Yii::request->post('username')就可以从返回的这个数组中取值了
总结:
1.在 Yii2 框架中要用封装的 post() 和 get() 方法, 而不要使用 $_POST $_GET 等方法,因为两者是不相等的。
2.Yii2 做 api 的时候,如果是 json 格式传递数据,一定不要忘记在 request component 中加上配置:
'request' => [ 'class' => 'yii\web\Request', 'parsers' => [ 'application/json' => 'yii\web\JsonParser', ],],