riverline/multipart-parser
Lightweight PHP multipart/form-data parser for handling HTTP uploads and mixed multipart bodies. Extracts fields and files from raw request streams with low memory use, suited for PSR-7 or custom servers needing reliable multipart boundary parsing.
Install via Composer:
composer require riverline/multipart-parser
Basic usage – parse a raw request body (e.g., from a PSR-7 Request or raw php://input):
use Riverline\MultipartParser\Parser;
$body = file_get_contents('php://input'); // or $psr7Request->getBody()->getContents()
$parser = new Parser($body);
foreach ($parser->getParts() as $part) {
if ($part->isFile()) {
echo "File: {$part->getFileName()} ({$part->getMediaType()})\n";
$content = $part->getContent(); // string contents of the file
} else {
echo "Field: {$part->getName()} = {$part->getContent()}\n";
}
}
First use case: Replace $_FILES/$_POST in API middleware (e.g., for a microframework, Swoole, or custom HTTP server) where superglobals aren’t populated or accessible.
Framework Integration (e.g., Slim, Laminas, custom stack)
Create a middleware that uses Parser to build a PSR-7-compatible body or augment the request with parsed fields/files:
$parsed = (new Parser($request->getBody()->getContents()))->toArray();
// $parsed['fields'] and $parsed['files'] are arrays of parsed data
$request = $request->withParsedBody($parsed['fields']);
Streaming large uploads
For memory efficiency, parse from a stream resource instead of loading the entire body:
$stream = fopen('php://temp', 'r+');
fwrite($stream, $rawMultipartBody);
rewind($stream);
$parser = new Parser($stream);
Custom file handling
Stream file contents to disk instead of loading into memory:
foreach ($parser->getParts() as $part) {
if ($part->isFile()) {
$destination = '/uploads/' . basename($part->getFileName());
file_put_contents($destination, $part->getContent()); // or use stream_copy_to_stream() for large files
}
}
Repeated field names
Auto-duplicates for repeated field names are grouped in arrays:
// For multipart with two `tags[]` parts ("php", "laravel")
$fields = $parser->toArray()['fields'];
// $fields['tags'] === ['php', 'laravel']
Invalid/malformed boundaries → Parser throws UnexpectedValueException. Always wrap parsing in try/catch.
try {
$parser = new Parser($body);
} catch (UnexpectedValueException $e) {
// Log error, return 400 Bad Request
}
Content-Transfer-Encoding: Only 7bit, 8bit, and binary are supported. quoted-printable or base64 encoding in parts will cause errors unless pre-decoded.
Memory management: For large files, avoid $part->getContent() — use getResource() instead and stream to disk or temp:
$resource = $part->getResource(); // stream resource
$out = fopen('/tmp/' . $part->getFileName(), 'w');
stream_copy_to_stream($resource, $out);
fclose($out);
No built-in validation: You must manually validate MIME types, extensions, sizes, etc. Check $part->getMediaType() and $part->getHeader('content-length').
File names not sanitized: Client-provided filenames may include path traversal (e.g., ../../evil.php). Always sanitize with basename() or pathinfo() before saving.
Works with php://input but not $_FILES: This library parses raw payloads — it’s for cases where PHP’s built-in parsing isn’t used (e.g., Content-Type: multipart/form-data sent via curl --data-binary without POST). It does not replace standard $_FILES handling.
Debug tip: Log the raw Content-Type header (especially boundary="...") — missing or malformed boundaries are the #1 cause of parsing failures.
How can I help you explore Laravel packages today?