File Uploads
This guide explains how to upload files and work with multipart form data using the Fetch HTTP package.
Basic File Upload
To upload a file, you need to use multipart form data. The simplest way is with the helper functions:
// Upload a file $response = fetch('https://api.example.com/upload', [ 'method' => 'POST', 'multipart' => [ [ 'name' => 'file', 'contents' => file_get_contents('/path/to/image.jpg'), 'filename' => 'upload.jpg', 'headers' => ['Content-Type' => 'image/jpeg'] ], [ 'name' => 'description', 'contents' => 'Profile picture upload' ] ] ]); // Check if upload was successful if ($response->successful()) { $result = $response->json(); echo "File uploaded successfully. URL: " . $result['url']; } else { echo "Upload failed with status: " . $response->status(); }
Using ClientHandler for File Uploads
You can also use the ClientHandler class for more control:
use Fetch\Http\ClientHandler; $client = ClientHandler::create(); $response = $client->withMultipart([ [ 'name' => 'file', 'contents' => file_get_contents('/path/to/document.pdf'), 'filename' => 'document.pdf', 'headers' => ['Content-Type' => 'application/pdf'] ], [ 'name' => 'document_type', 'contents' => 'invoice' ], [ 'name' => 'description', 'contents' => 'Monthly invoice #12345' ] ])->post('https://api.example.com/documents');
Uploading Multiple Files
You can upload multiple files in a single request:
$response = fetch('https://api.example.com/gallery', [ 'method' => 'POST', 'multipart' => [ [ 'name' => 'files[]', 'contents' => file_get_contents('/path/to/image1.jpg'), 'filename' => 'image1.jpg', 'headers' => ['Content-Type' => 'image/jpeg'] ], [ 'name' => 'files[]', 'contents' => file_get_contents('/path/to/image2.jpg'), 'filename' => 'image2.jpg', 'headers' => ['Content-Type' => 'image/jpeg'] ], [ 'name' => 'album', 'contents' => 'Vacation Photos' ] ] ]);
Uploading From a Stream
For large files, you can upload directly from a stream rather than loading the entire file into memory:
// Open file as a stream $stream = fopen('/path/to/large-file.zip', 'r'); $response = fetch('https://api.example.com/upload', [ 'method' => 'POST', 'multipart' => [ [ 'name' => 'file', 'contents' => $stream, 'filename' => 'large-file.zip', 'headers' => ['Content-Type' => 'application/zip'] ], [ 'name' => 'description', 'contents' => 'Large file upload' ] ] ]); // Don't forget to close the stream fclose($stream);
File Upload with Progress Tracking
For large file uploads, you might want to track progress. This can be done using Guzzle's progress middleware:
use Fetch\Http\ClientHandler; use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use GuzzleHttp\Middleware; use GuzzleHttp\Psr7\Utils; // Create a function to track upload progress $progress = function ($totalBytes, $downloadedBytes, $uploadedBytes, $uploadTotal) { if ($uploadTotal > 0) { $percent = round(($uploadedBytes / $uploadTotal) * 100); echo "Upload progress: {$percent}% ({$uploadedBytes}/{$uploadTotal} bytes)\n"; } }; // Create a handler stack with the progress middleware $stack = HandlerStack::create(); $stack->push(Middleware::tap(null, $progress)); // Create a custom Guzzle client with the stack $guzzleClient = new Client([ 'handler' => $stack ]); // Create a client handler with the custom client $client = ClientHandler::createWithClient($guzzleClient); // Create a file stream $stream = Utils::streamFor(fopen('/path/to/large-file.mp4', 'r')); // Upload the file $response = $client->withMultipart([ [ 'name' => 'file', 'contents' => $stream, 'filename' => 'video.mp4', 'headers' => ['Content-Type' => 'video/mp4'] ] ])->post('https://api.example.com/upload'); // The progress function will be called during the upload
Handling File Upload Errors
File uploads can fail for various reasons. Here's how to handle common errors:
try { $response = fetch('https://api.example.com/upload', [ 'method' => 'POST', 'multipart' => [ [ 'name' => 'file', 'contents' => file_get_contents('/path/to/image.jpg'), 'filename' => 'upload.jpg' ] ] ]); if ($response->isUnprocessableEntity()) { $errors = $response->json()['errors'] ?? []; foreach ($errors as $field => $messages) { echo "Error with {$field}: " . implode(', ', $messages) . "\n"; } } elseif ($response->status() === 413) { echo "File too large. Maximum size exceeded."; } elseif (!$response->successful()) { echo "Upload failed with status: " . $response->status(); } else { echo "Upload successful!"; } } catch (\Fetch\Exceptions\NetworkException $e) { echo "Network error: " . $e->getMessage(); } catch (\Fetch\Exceptions\RequestException $e) { echo "Request error: " . $e->getMessage(); } catch (\Exception $e) { echo "Error: " . $e->getMessage(); }
Multipart Form Specifications
When setting up multipart form data, each part needs these elements:
name
: The form field name (required)contents
: The content of the field (required) - can be a string, resource, or StreamInterfacefilename
: The filename for file uploads (optional)headers
: An array of headers for this part (optional)
File Upload with Authentication
Many APIs require authentication for file uploads:
$response = fetch('https://api.example.com/upload', [ 'method' => 'POST', 'token' => 'your-api-token', // Bearer token authentication 'multipart' => [ [ 'name' => 'file', 'contents' => file_get_contents('/path/to/file.jpg'), 'filename' => 'upload.jpg' ] ] ]);
Or using the ClientHandler:
$response = ClientHandler::create() ->withToken('your-api-token') ->withMultipart([ [ 'name' => 'file', 'contents' => file_get_contents('/path/to/file.jpg'), 'filename' => 'upload.jpg' ] ]) ->post('https://api.example.com/upload');
Using Enums for Content Types
You can use the ContentType
enum for specifying content types in your file uploads:
use Fetch\Enum\ContentType; use Fetch\Http\ClientHandler; $response = ClientHandler::create() ->withMultipart([ [ 'name' => 'file', 'contents' => file_get_contents('/path/to/document.pdf'), 'filename' => 'document.pdf', 'headers' => ['Content-Type' => ContentType::PDF->value] ], [ 'name' => 'image', 'contents' => file_get_contents('/path/to/image.jpg'), 'filename' => 'image.jpg', 'headers' => ['Content-Type' => ContentType::JPEG->value] ] ]) ->post('https://api.example.com/upload');
File Download
Although not strictly an upload, you might also need to download files:
$response = fetch('https://api.example.com/files/document.pdf'); // Save the file to disk file_put_contents('downloaded-document.pdf', $response->body()); // Or get it as a stream $stream = $response->blob(); $fileContents = stream_get_contents($stream); fclose($stream); // Or get it as a binary string $binaryData = $response->arrayBuffer();
Handling Large File Downloads
For large file downloads, you can use streaming:
use Fetch\Http\ClientHandler; $client = ClientHandler::create(); $response = $client ->withStream(true) // Enable streaming ->get('https://api.example.com/large-files/video.mp4'); // Open a file to save the download $outputFile = fopen('downloaded-video.mp4', 'w'); // Get the response body as a stream $body = $response->getBody(); // Read the stream in chunks and write to the file while (!$body->eof()) { fwrite($outputFile, $body->read(4096)); // Read 4KB at a time } // Close the file fclose($outputFile);
Asynchronous File Uploads
For large files, you might want to use asynchronous uploads to prevent blocking:
use function async; use function await; $result = await(async(function() { // Open file as a stream $stream = fopen('/path/to/large-file.zip', 'r'); // Upload the file $response = await(async(function() use ($stream) { return fetch('https://api.example.com/upload', [ 'method' => 'POST', 'multipart' => [ [ 'name' => 'file', 'contents' => $stream, 'filename' => 'large-file.zip' ] ], 'timeout' => 300 // 5-minute timeout for large uploads ]); })); // Close the stream fclose($stream); return $response->json(); })); echo "Upload complete with ID: " . $result['id'];
Best Practices
Check File Size: Verify file sizes before uploading to avoid timeouts or server rejections.
Set Appropriate Timeouts: Large file uploads may need longer timeouts:
php$response = fetch('https://api.example.com/upload', [ 'method' => 'POST', 'timeout' => 300, // 5-minute timeout 'multipart' => [/* ... */] ]);
Use Streams for Large Files: Avoid loading large files entirely into memory.
Add Retry Logic: File uploads are prone to network issues, so add retry logic:
php$response = fetch_client() ->retry(3, 1000) // 3 retries with 1 second initial delay ->retryStatusCodes([408, 429, 500, 502, 503, 504]) ->withMultipart([/* ... */]) ->post('https://api.example.com/upload');
Validate Before Uploading: Check file types, sizes, and other constraints before uploading.
Include Progress Tracking: For large files, provide progress feedback to users.
Log Upload Attempts: Log uploads for troubleshooting and auditing:
phpuse Monolog\Logger; use Monolog\Handler\StreamHandler; // Create a logger $logger = new Logger('uploads'); $logger->pushHandler(new StreamHandler('logs/uploads.log', Logger::INFO)); // Set the logger on the client $client = fetch_client(); $client->setLogger($logger); // Make the upload request $response = $client->withMultipart([/* ... */]) ->post('https://api.example.com/upload');
Next Steps
- Learn about Authentication for secured file uploads
- Explore Error Handling for robust upload error management
- See Retry Handling for handling transient upload failures
- Check out Asynchronous Requests for non-blocking uploads