-
-
Save JhowRaul10/cb2f9670ad0a8aa2fc32d263f948342a to your computer and use it in GitHub Desktop.
<?php | |
namespace App\Http\Middleware; | |
use Closure; | |
use Symfony\Component\HttpFoundation\ParameterBag; | |
use Illuminate\Support\Arr; | |
/** | |
* @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); | |
$arr = array( | |
'name' => $fileName, | |
'type' => $headers['content-type'], | |
'tmp_name' => $localFileName, | |
'error' => 0, | |
'size' => filesize($localFileName) | |
); | |
if(substr($fieldName, -2, 2) == '[]') { | |
$fieldName = substr($fieldName, 0, strlen($fieldName)-2); | |
} | |
if(array_key_exists($fieldName, $files)) { | |
array_push($files[$fieldName], $arr); | |
} else { | |
$files[$fieldName] = array($arr); | |
} | |
// register a shutdown function to cleanup the temporary file | |
register_shutdown_function(function() use($localFileName) { | |
unlink($localFileName); | |
}); | |
} else { | |
parse_str($fieldName.'=__INPUT__', $parsedInput); | |
$dottedInput = Arr::dot($parsedInput); | |
$targetInput = Arr::add([], array_keys($dottedInput)[0], $content); | |
$data = array_merge_recursive($data, $targetInput); | |
} | |
} | |
} | |
$fields = new ParameterBag($data); | |
return ["inputs" => $fields->all(), "files" => $files]; | |
} | |
} |
Digging deeper, it happened that the validation is failing because of Symfony
UploadedFile.php
class testing file upload validity internally using PHPis_uploaded_file
function in theisValid()
function, and this PHP function tells whether the file was uploaded via HTTP POST. More here.Did you find a workaround to get this work?
Did you try the PECL extension? That basically populates the POST data so should fix your issue. I'd be interested to find out if that is the case.
Hey guys! Ty for the great workaround!! But I've found one thing that I suppose that isn't intended...
When setting a file to the request, $request->image
should return directly the UploadedFile, but insteads it returns an array with the UploadedFile being the index 0, I THINK this isn't the expected return, as the middleware is trying to workaround, and laravel wouldn't handle that file like that... I've solved this editing the line 97 from: $files[$fieldName] = array($arr);
to $files[$fieldName] = $arr;
but I didn't tested enough to tell if that would affect anything else since my application is new and still pretty small. If you can give me some feedback about that, would appreciate! And again: tyvm!
The extension didn't fix for me. $request->allFiles() is still empty on PUT requests
Unfortunately, not. Until the PHP team resolves that issue, no workaround for that.