-
-
Save Stunext/9171b7a8f3633b0b601a0feb8088dca1 to your computer and use it in GitHub Desktop.
<?php | |
namespace App\Http\Middleware; | |
use Closure; | |
use Symfony\Component\HttpFoundation\ParameterBag; | |
/** | |
* @author https://github.com/Stunext | |
* | |
* PHP, and by extension, Laravel does not support multipart/form-data requests when using any request method other than POST. | |
* This limits the ability to implement RESTful architectures. This is a middleware for Laravel 5.7 that manually decoding | |
* the php://input stream when the request type is PUT, DELETE or PATCH and the Content-Type header is mutlipart/form-data. | |
* | |
* The implementation is based on an example by [netcoder at stackoverflow](http://stackoverflow.com/a/9469615). | |
* This is necessary due to an underlying limitation of PHP, as discussed here: https://bugs.php.net/bug.php?id=55815. | |
*/ | |
class HandlePutFormData | |
{ | |
/** | |
* Handle an incoming request. | |
* | |
* @param \Illuminate\Http\Request $request | |
* @param \Closure $next | |
* @return mixed | |
*/ | |
public function handle($request, Closure $next) | |
{ | |
if ($request->method() == 'POST' or $request->method() == 'GET') | |
{ | |
return $next($request); | |
} | |
if (preg_match('/multipart\/form-data/', $request->headers->get('Content-Type')) or preg_match('/multipart\/form-data/', $request->headers->get('content-type'))) | |
{ | |
$parameters = $this->decode(); | |
$request->merge($parameters['inputs']); | |
$request->files->add($parameters['files']); | |
} | |
return $next($request); | |
} | |
public function decode() | |
{ | |
$files = array(); | |
$data = array(); | |
// Fetch content and determine boundary | |
$rawData = file_get_contents('php://input'); | |
$boundary = substr($rawData, 0, strpos($rawData, "\r\n")); | |
// Fetch and process each part | |
$parts = array_slice(explode($boundary, $rawData), 1); | |
foreach ($parts as $part) { | |
// If this is the last part, break | |
if ($part == "--\r\n") { | |
break; | |
} | |
// Separate content from headers | |
$part = ltrim($part, "\r\n"); | |
list($rawHeaders, $content) = explode("\r\n\r\n", $part, 2); | |
$content = substr($content, 0, strlen($content) - 2); | |
// Parse the headers list | |
$rawHeaders = explode("\r\n", $rawHeaders); | |
$headers = array(); | |
foreach ($rawHeaders as $header) { | |
list($name, $value) = explode(':', $header); | |
$headers[strtolower($name)] = ltrim($value, ' '); | |
} | |
// Parse the Content-Disposition to get the field name, etc. | |
if (isset($headers['content-disposition'])) { | |
$filename = null; | |
preg_match('/^form-data; *name="([^"]+)"(; *filename="([^"]+)")?/', $headers['content-disposition'], $matches); | |
$fieldName = $matches[1]; | |
$fileName = (isset($matches[3]) ? $matches[3] : null); | |
// If we have a file, save it. Otherwise, save the data. | |
if ($fileName !== null) { | |
$localFileName = tempnam(sys_get_temp_dir(), 'sfy'); | |
file_put_contents($localFileName, $content); | |
$files[$fieldName] = array( | |
'name' => $fileName, | |
'type' => $headers['content-type'], | |
'tmp_name' => $localFileName, | |
'error' => 0, | |
'size' => filesize($localFileName) | |
); | |
// register a shutdown function to cleanup the temporary file | |
register_shutdown_function(function() { | |
unlink($localFileName); | |
}); | |
} else { | |
$data[$fieldName] = $content; | |
} | |
} | |
} | |
$fields = new ParameterBag($data); | |
return ["inputs" => $fields->all(), "files" => $files]; | |
} | |
} |
Couple of things:
Remove notice:
--- register_shutdown_function(function() {
+++ register_shutdown_function(function() use($localFileName) {
unlink($localFileName);
});
HTML Arrays
If you have HTML array fields (e.g. <input name="foo[]" value="1">
the $fieldName
would be wrong. If you dump $request->all()
you should have
[
'foo' => [ 1 ]
]
And, instead, you have: [ "foo[]" => 1 ]
.
Any ideas how to fix this?
thanks
This worked for me! Thank you so much
thanks bro
thanks bro
HTML Arrays
If you have HTML array fields (e.g.
<input name="foo[]" value="1">
the$fieldName
would be wrong. If you dump$request->all()
you should have[ 'foo' => [ 1 ] ]
And, instead, you have:
[ "foo[]" => 1 ]
.Any ideas how to fix this?
For this issue, I use PHP parse_str function to parse the array key, then flatten, pull the first dotted array key, and merge with the existing one.
Just add use Illuminate\Support\Arr;
at the top of the file, and replace line 91 with:
parse_str($fieldName.'=__INPUT__', $parsedInput);
$dottedInput = Arr::dot($parsedInput);
$targetInput = Arr::add([], array_key_first($dottedInput), $content);
$data = array_merge_recursive($data, $targetInput);
I hope this can help all of you.
Reference: https://gist.github.com/devmycloud/df28012101fbc55d8de1737762b70348#file-parseinputstream-php-L79
Works only for HTTP environment. Fails for HTTPS environment with
explode(): Empty delimiter {"exception":"[object] (ErrorException(code: 0): explode(): Empty delimiter at /application/app/Http/Middleware/HandlePutFormData.php:54)
thx man
HTML Arrays
If you have HTML array fields (e.g.
<input name="foo[]" value="1">
the$fieldName
would be wrong. If you dump$request->all()
you should have[ 'foo' => [ 1 ] ]
And, instead, you have:
[ "foo[]" => 1 ]
.
Any ideas how to fix this?For this issue, I use PHP parse_str function to parse the array key, then flatten, pull the first dotted array key, and merge with the existing one.
Just add
use Illuminate\Support\Arr;
at the top of the file, and replace line 91 with:parse_str($fieldName.'=__INPUT__', $parsedInput); $dottedInput = Arr::dot($parsedInput); $targetInput = Arr::add([], array_key_first($dottedInput), $content); $data = array_merge_recursive($data, $targetInput);
I hope this can help all of you.
Reference: https://gist.github.com/devmycloud/df28012101fbc55d8de1737762b70348#file-parseinputstream-php-L79
array_key_first not working:
$targetInput = Arr::add([], array_key_first($dottedInput), $content);
Use:
$targetInput = Arr::add([], array_keys($dottedInput)[0], $content);
This solution returns in $request->all()
but returns nothing in $request->file('arquivos')
I noticed one thing, that when doing POST without middleware the result (which works normally):
'arquivos' =>
array (
0 =>
Illuminate\Http\UploadedFile::__set_state(array(
'test' => false,
'originalName' => 'BUG_Refeicoes_Lanches_T12.png',
'mimeType' => 'image/png',
'size' => 21626,
'error' => 0,
'hashName' => NULL,
)),
),
And when doing PUT with middleware the result is:
'arquivos[]' =>
Illuminate\Http\UploadedFile::__set_state(array(
'test' => false,
'originalName' => 'BUG_Refeicoes2.png',
'mimeType' => 'image/png',
'size' => 21626,
'error' => 0,
'hashName' => NULL,
)),
As a consequence, it only returns in $request->all()
, but it does not return in $request->file('arquivos')
, and also returns only 1 file with the middleware, even if I upload 2 files (for POST Request without middleware returns 2 normal).
I made the corrections in fork: https://gist.github.com/JhonatanRaul/cb2f9670ad0a8aa2fc32d263f948342a
Tyvm folks,
Good Jobs!
Thank you very much.
I thought it had sorted out my problem but no.
The best way is to use method spoofing in your request body.
const formData = new FormData()
formData.append('_method', 'PUT')
Then make your XMLHTTPRequest as POST laravel will redirect this to the update method
i want to get the result as the picture above rather than this,
Can anyone help me ? I was using the method that suggest by @JhonatanRaul for changing the line 91 by use PHP parse_str function to parse the array key, then flatten, pull the first dotted array key, and merge with the existing one.
What
$localFileName
stands for?