Easy to use service for chunked upload with several js providers on top of Laravel's file upload.
- Installation
- Usage
- Supports
- Features
- Basic documentation
- Example
- Providers/Handlers
- Changelog
- Contribution or overriding
- Suggested frontend libs
Install via composer
composer require pion/laravel-chunk-upload
Add the service provider
\Pion\Laravel\ChunkUpload\Providers\ChunkUploadServiceProvider::class
Optional
Publish the config
Run the publish command to copy the translations (Laravel 5.2 and above)
php artisan vendor:publish --provider="Pion\Laravel\ChunkUpload\Providers\ChunkUploadServiceProvider"
In your own controller create the FileReceiver
, more in example.
- Laravel 5+
- blueimp-file-upload - partial support (simple chunked and single upload)
- Plupload
- Chunked uploads uses chunked writing aswell to minimize the memory footprint
- Storing per Laravel Session to prevent overwrite all TMP files are stored with session token
- Clear command and schedule the package registers the shedule command (uploads:clear) that will clear all unfinished chunk uploads
- Automatic handler selection since
v0.2.4
you can use automatic detection selection the handler to use from the current supported providers. You can also register your own handler to the automatic detection (more in Handlers) - Supports cross domain request (must change the config - see Cross domain request section in readme)
- Create a Upload controller. If using Laravel 5.4 and above, add your upload controller into
web
route. If necessary, add toapi
routes and change the config to use IP for chunk name. - Implement your JS (you can use the same code as below or in example repository)
- Check if your library is sending
cookie
, the chunk naming uses session (you can change it - will use only IP address)
You must create the file receiver with the file index (in the Request->file
), the current request and the desired handler class (currently the ContentRangeUploadHandler::class
)
Then you can use methods:
determines if the file object is in the request
####receive()
Tries to handle the upload request. If the file is not uploaded, returns false. If the file
is present in the request, it will create the save object.
If the file in the request is chunk, it will create the ChunkSave
object, otherwise creates the SingleSave
which doesn't nothing at this moment.
The full example (Laravel 5.4 - works same on previous versions) can be found in separate repo: laravel-chunk-upload-example
Written for jQuery-File-Upload
$element.fileupload({
url: "upload_url",
maxChunkSize: 1000000,
method: "POST",
sequentialUploads: true,
formData: function(form) {
//laravel token for communication
return [{name: "_token", value: $form.find("[name=_token]").val()}];
},
progressall: function(e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
console.log(progress+"%");
}
})
.bind('fileuploadchunksend', function (e, data) {
//console.log("fileuploadchunksend");
})
.bind('fileuploadchunkdone', function (e, data) {
//console.log("fileuploadchunkdone");
})
.bind('fileuploadchunkfail', function (e, data) {
console.log("fileuploadchunkfail")
});
- Create laravel controller
UploadController
and create the file receiver with the desired handler. - You must import the full namespace in your controller (
use
). - When upload is finished, don't forget to move the file to desired folder (as standard UploadFile implementation). You can check the example project.
- An example of save function below the handler usage
When you support multiple upload providers. Full Controller in example
/**
* Handles the file upload
*
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
*
* @throws UploadMissingFileException
*/
public function upload(Request $request) {
// create the file receiver
$receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request));
// check if the upload is success
if ($receiver->isUploaded()) {
// receive the file
$save = $receiver->receive();
// check if the upload has finished (in chunk mode it will send smaller files)
if ($save->isFinished()) {
// save the file and return any response you need
return $this->saveFile($save->getFile());
} else {
// we are in chunk mode, lets send the current progress
/** @var AbstractHandler $handler */
$handler = $save->handler();
return response()->json([
"done" => $handler->getPercentageDone(),
]);
}
} else {
throw new UploadMissingFileException();
}
}
We set the handler we want to use always.
/**
* Handles the file upload
*
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
*
* @throws UploadMissingFileException
*/
public function upload(Request $request) {
// create the file receiver
$receiver = new FileReceiver("file", $request, ContentRangeUploadHandler::class);
// check if the upload is success
if ($receiver->isUploaded()) {
// receive the file
$save = $receiver->receive();
// check if the upload has finished (in chunk mode it will send smaller files)
if ($save->isFinished()) {
// save the file and return any response you need
return $this->saveFile($save->getFile());
} else {
// we are in chunk mode, lets send the current progress
/** @var ContentRangeUploadHandler $handler */
$handler = $save->handler();
return response()->json([
"start" => $handler->getBytesStart(),
"end" => $handler->getBytesEnd(),
"total" => $handler->getBytesTotal()
]);
}
} else {
throw new UploadMissingFileException();
}
}
/**
* Handles the file upload
*
* @param Request $request
*
* @param Int $fileIndex
*
* @return \Illuminate\Http\JsonResponse
*
* @throws UploadMissingFileException
*/
public function upload(Request $request) {
// Response for the files - completed and uncompleted
$files = [];
// Get array of files from request
$files = $request->file('files');
if (!is_array($files)) {
throw new UploadMissingFileException();
}
// Loop sent files
foreach ($files as $file) {
// Instead of passing the index name, pass the UploadFile object from the $files array we are looping
// Create the file receiver via dynamic handler
$receiver = new FileReceiver($file, $request, HandlerFactory::classFromRequest($request));
// or via static handler usage
$receiver = new FileReceiver($file, $request, ContentRangeUploadHandler::class);
if ($receiver->isUploaded()) {
// receive the file
$save = $receiver->receive();
// check if the upload has finished (in chunk mode it will send smaller files)
if ($save->isFinished()) {
// save the file and return any response you need
$files[] = $this->saveFile($save->getFile());
} else {
// we are in chunk mode, lets send the current progress
/** @var ContentRangeUploadHandler $handler */
$handler = $save->handler();
// Add the completed file
$files[] = [
"start" => $handler->getBytesStart(),
"end" => $handler->getBytesEnd(),
"total" => $handler->getBytesTotal(),
"finished" => false
];
}
}
}
return response()->json($files);
}
This example moves the file (merged chunks) into own upload directory. This will ensure that the cunk will be deleted after move.
/**
* Saves the file
*
* @param UploadedFile $file
*
* @return \Illuminate\Http\JsonResponse
*/
protected function saveFile(UploadedFile $file)
{
$fileName = $this->createFilename($file);
// Group files by mime type
$mime = str_replace('/', '-', $file->getMimeType());
// Group files by the date (week
$dateFolder = date("Y-m-W");
// Build the file path
$filePath = "upload/{$mime}/{$dateFolder}/";
$finalPath = storage_path("app/".$filePath);
// move the file name
$file->move($finalPath, $fileName);
return response()->json([
'path' => $filePath,
'name' => $fileName,
'mime_type' => $mime
]);
}
Add a route to your controller in the web
route (if you want to use the session).
Route::post('upload', 'UploadController@upload');
Clears old chunks from the chunks folder, uses the config to detect which files can be deleted via the last edit time clear.timestamp
.
The scheduler can be disabled by a config clear.schedule.enabled
or the cron time can be changed in clear.schedule.cron
(don't forget to setup your scheduler in the cron)
php artisan uploads:clear
In default we use client browser info to generate unique name for the chunk file (support same file upload at same time).
The logic supports also using the Session::getId()
, but you need to force your JS library to send the cookie.
You can update the chunk.name.use
settings for custom usage.
When using uploader for the cross domain request you must setup the chunk.name.use
to browser logic instead of session.
"use" => [
"session" => false, // should the chunk name use the session id? The uploader muset send cookie!,
"browser" => true // instead of session we can use the ip and browser?
]
Then setup your laravel Setup guide
Use AbstractHandler
for type hint or use a specific handler to se additional methods.
- supported by blueimp-file-upload
- uses the Content-range header with the bytes range
getBytesStart()
- returns the starting bytes for current requestgetBytesEnd()
- returns the ending bytes for current requestgetBytesTotal()
- returns the total bytes for the file
- Supported by plupload
- uses the chunks numbers from the request
See the Contribution
section in Readme
You can use the automatic detection of the correct handler (provider) by using the HandlerFactory::classFromRequest
as
a third parameter when constructing the FileReceiver
.
$receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request));
The default fallback class is stored in the HandlerFactory (default SingleUploadHandler::class
).
You can change it globally by calling
HandlerFactory::setFallbackHandler(CustomHandler::class)
or pass as second parameter when using
HandlerFactory::classFromRequest($request, CustomHandler::class)
- Added support for passing file object instead of fileIndex (example: multiple files in a request). Change discussion in #7 (@RAZORzdenko), merged in #8
- Updated composer to support Laravel 5.4
- Support for cross domain requests (only chunk naming)
- Added support for plupload package
- Added automatic handler selection based on the request
The package supports the Laravel Filesystem. Because of this, the storage must be withing the app folder storage/app/
or custom drive (only local) - can be set in the config storage.disk
.
The cloud drive is not supported because of the chunked write (probably could be changed to use a stream) and the resulting object - UploadedFile
that supports only full path.
- add more providers
- add facade for a quick usage with callback and custom response based on the handler
- cron to delete uncompleted files
since v0.2.0
- file per session (to support multiple)
since v0.1.1
- add a config with custom storage location
since v0.2.0
- add an example project
since v1.0.1
- add support to different drive than a local drive
See CONTRIBUTING.md for how to contribute changes. All contributions are welcome.
- https://github.com/lemonCMS/react-plupload
- https://github.com/moxiecode/plupload
- https://github.com/blueimp/jQuery-File-Upload
laravel-chunk-upload was written by Martin Kluska and is released under the MIT License.
Copyright (c) 2016 Martin Kluska