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:
use function Fetch\Http\fetch;
// 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);
Using the Request Class
You can also create a multipart request using the Request class:
use Fetch\Http\Request;
use Fetch\Enum\Method;
// Create a multipart request
$request = Request::multipart(
Method::POST,
'https://api.example.com/upload',
[
[
'name' => 'file',
'contents' => file_get_contents('/path/to/image.jpg'),
'filename' => 'upload.jpg',
'headers' => ['Content-Type' => 'image/jpeg']
],
[
'name' => 'description',
'contents' => 'Profile picture upload'
]
]
);
// Send the request using fetch()
$response = fetch($request);
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 (\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');
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);
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);
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('https://api.example.com/upload', [ 'method' => 'POST', 'retries' => 3, 'multipart' => [/* ... */] ]);
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.
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