cybex/laravel-lodor

Provides easy support for processing chunked uploads in Laravel.


README

Latest Version on Packagist Packagist Downloads Github build status GitHub pull-requests GitHub issues GitHub contributors Laravel Version

This package for Laravel 6.x or newer provides an easy way to implement simple as well as chunked uploading from frontend libraries like DropzoneJS or ResumableJS and implement custom synchronous or asynchronous post-processing through (queued) listeners thanks to its use of Laravel Events.

The package is not available for older Laravel versions because the support for versions below 6.0.0 has run out.

Installation

You can install the package via composer:

composer require cybex/laravel-lodor

Security

Please note that by default, the upload and polling routes are protected by web and auth middleware for security reasons. This means that Lodor will only work on authenticated routes and when called with a CSRF token. Otherwise you will get a HTTP 401 response when trying to upload or poll.

Please refer to the Laravel Documentation on CSRF protection and Authentication for further reading.

Customizing the Route Middleware

It is encouraged to leave the web middleware active at all times to ensure protection against CSRF. However, there might be times when you want to allow uploads by users without logging in to your site, e.g. on contact forms.

You can customize the middleware that is applied to the routes registered by Lodor by adjusting the route_middleware array in the Configuration.

Usage

To get started with a simple HTML file upload, the only thing you really have to do is to set the action of your file upload form to the Lodor upload route:

<form id="upload-form" enctype="multipart/form-data" method="post" action="{{ Lodor::getUploadRoute() }}">
    @csrf
    <label for="file-input">Upload a file with Lodor:</label>
    <input type="file" name="file" id="file-input" multiple />
    <input type="submit">
</form>

By default, Lodor registers a POST route at /uploadmedia, and all simple uploads go straight to the lodor/uploads directory in the storage path of your Laravel application.

The HTML form above will upload the file to your storage directory and, by default, return a JSON with a success indicator and uuid like:

{"success":true,"uuid":"ffb3dfe7-9029-4b9a-abfe-5e7485592561"}

This setup is useful for asynchronous uploads using Javascript, particularly when using libraries like Dropzone.js or Resumable.js.

Redirecting to a Controller after Upload

If you want to process the form yourself instead after the upload completed, you may define a named route by the name of lodor_uploaded like this:

Route::post('/uploaded')->uses('SomeController@uploaded')->name('lodor_uploaded');

If this named route exists, Lodor will automatically redirect the request to the specified controller action instead of returning a JSON response. The controller method should be declared as follows:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class SomeController extends Controller 
{
   function uploaded(Request $request, bool $success, string $uuid, array $metadata, string $errorMessage = null) {
        // Do something here and handle the request returning some response, view or redirect.
    }
}
  • $request contains all request data of the file upload form.
  • $success is true if the upload succeeded, and false if not.
  • $uuid contains the unique id of the upload.
  • $metadata is an array containing detail info about the uploaded file.
  • $errormessage contains the error message if the upload failed or is null otherwise.

Chunked uploads

Lodor automatically merges upload chunks back into a single file. To prevent interruptions due to exceeding the maximum execution time for PHP scripts, Lodor uses worker queues by default. If you cannot or do not wish to use workers, you should set the LODOR_MERGE_ASYNC=false environment variable or set the merge_chunks.run_async config setting to false (see Configuration for details).

Configuration

Lodor was created with a setup in mind that works out of the box for most situations. However, you can publish its configuration file to your application's config directory to customize the settings to your needs. You can publish the config using the following command:

php artisan vendor:publish --provider="Cybex\Lodor\LodorServiceProvider" --tag=config

Most of the settings can also be adjusted by environment settings that you can put in your .env file as needed.

The available options with their corresponding env settings and defaults are:

Cleanup

Lodor automatically runs a cleanup if the UploadFailed event is triggered after an upload fails or the UploadFinished event is triggered after successful completion. In case of a failing upload, all files - chunks and merged - are always forcibly deleted regardless of your configuration settings.

In case of a successfully finished upload, the behavior depends on a number of configuration settings. By default, Lodor will delete all existing file chunks during cleanup, but not the merged files in the upload disk.

To change this default behavior, you can set lodor.auto_cleanup_chunks to false to leave the chunks in place after uploading, and lodor.auto_cleanup to true to always delete the finished uploads once they are completed (as indicated by the UploadFinished event).

Using auto-cleanup

In a basic setup with no further event listeners registered, Lodor always triggers the UploadFinished event once the non-chunked upload succeeded or the chunked upload was successfully merged. Therefore, auto_cleanup is set to false by default. If you set it to true, your uploads would otherwise be gone the second they are finished.

If you want to use auto_cleanup, you need to make sure that you register a listener for the FileUploaded event, usually by adding it to your EventServiceProvider (see Registering Events & Listeners in the Laravel docs for details).

You can simply use Laravel's generator to create a listener class for you, e.g.

artisan make:listener -e "\Cybex\Lodor\Events\FileUploaded" -- FileUploadedListener

Then include it in your EventServiceProvider $listener property:

use App\Listeners\FileUploadedListener;
use Cybex\Lodor\Events\FileUploaded;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        FileUploaded::class => [
            FileUploadedListener::class,
        ],
    ];

Inside your FileUploadedListener class, you should then process the file as needed in the handle() method and trigger the UploadFinished event to indicate that you are done processing and the files can be cleaned up:

    /**
     * Handle the event.
     *
     * @param  FileUploaded  $event
     * @return void
     */
    public function handle(FileUploaded $event)
    {
        $uuid     = $event->uuid;
        $metadata = $event->metadata;

        // You are also responsible to post updates on the status of the process using Lodor::setUploadStatus()
        // to keep the frontend up to date on the progress and any info you want to publish along with it.
        // After the server upload finishes, the upload is put in "waiting" state until the listener(s)
        // process(es) the upload and set the status to "done" state.
        Lodor::setUploadStatus($event->uuid,
            'done',
            __('Server upload finished.'),
            __('Upload complete.', ['uuid' => $uuid]),
            100,
            $metadata);

        // Then fire the UploadFinished event to signalize that the upload has completed processing.
        event(new UploadFinished($event->uuid, $event->metadata));
    }

Cleaning up manually

You may also choose to keep auto_cleanup disabled and do the cleanup yourself. You can do so by following the steps above and add

Lodor::cleanupUpload($event->uuid, true);

to your listener's handle() method. The second parameter specifies if all files should be forcibly deleted, regardless of the config settings.

Caveats

Sometimes, files might not be cleaned up at all, either because the cache info of the upload was deleted, your listener(s) are crashing or if you use queued event listeners and your job queue is failing or not running at all. To make sure leftover files are cleaned up, you may want to schedule a cron job that deletes old files from the lodor_chunked and lodor_uploads storage disks and the according info from the cache periodically. In future versions of Lodor, it is planned to implement both a Listener and an artisan command to clean up periodically.

Testing

composer test

To do

  • Cleanup Command for leftover uploads.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email info@lupinitylabs.com instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

Laravel Package Boilerplate

This package was generated using the Laravel Package Boilerplate (thanks, Marcel!).