Laravel 接收PUT/PATCH/DELETE请求的值

最近在调试PUT接口时,控制器中Request对象无法正常获取表单内容,发现这是Symfony组件的经典问题。

What

当时的场景是前端ajax使用PUT方式提交了一个multipart/form-data的请求,但是Request对象无法使用get()获取到数据。

Why

Laravel的Illuminate\Http\Request对象继承是Symfony\Component\HttpFoundation\Request,在获取请求表单内容的代码如下[1]:

1
2
3
4
5
6
7
8
9
10
11
public static function createFromGlobals()
{
$request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);
if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
&& \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
) {
parse_str($request->getContent(), $data);
$request->request = new ParameterBag($data);
}
return $request;
}

由于PHP原生只支持$_GET$_POST,所以PUTPATCHDELETE的表单内容要从$request->getContent()中解析,但这里进行解析有一个条件是请求头Content-Typeapplication/x-www-form-urlencoded,所以当我使用multipart/form-dataContent-Type进行PUT请求时,Request对象就无法正常解析。

How

解决方案有以下三个:

请求前

  1. Content-Type设置为application/x-www-form-urlencoded
  2. 覆盖请求头method: 将请求头method设为POST,同时将请求头X-HTTP-METHOD-OVERRIDE_method(url参数或请求体皆可)设为PUT/PATCH/DELETE

Laravel官方给出的建议方案[2]也是方案二中的后者。

1
2
3
4
5
6
7
8
9
10
11
12
if (! function_exists('method_field')) {
/**
* Generate a form field to spoof the HTTP verb used by forms.
*
* @param string $method
* @return \Illuminate\Support\HtmlString
*/
function method_field($method)
{
return new HtmlString('<input type="hidden" name="_method" value="'.$method.'">');
}
}

Symfony包中默认是禁用$httpMethodParameterOverride,而Laravel中默认开启。禁用时,只能通过请求头X-HTTP-METHOD-OVERRIDE覆盖。开启时,覆盖的优先级为:请求头X-HTTP-METHOD-OVERRIDE > 请求体_method > url参数_method > 请求头method [3]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function getMethod()
{
if (null === $this->method) {
$this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));

if ('POST' === $this->method) {
if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
$this->method = strtoupper($method);
} elseif (self::$httpMethodParameterOverride) {
$this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST')));
}
}
}

return $this->method;
}

请求后

  1. 继承Request对象补充对multipart/form-data类型的内容解析

Reference

https://learnku.com/laravel/t/14028/how-does-laravel-put-receive-values#reply56870