I came up with a cool way of uploading files inspired of form request mechanism. Tell me what you think about it :
An interface for uploaded files :
<?php namespace App\Http\Files;
interface UploadWhenResolvedInterface {
public function upload();
}
Call the upload method when it gets resolved :
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Http\Files\UploadWhenResolvedInterface;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->app->resolving(function(UploadWhenResolvedInterface $instance, $app)
{
$instance->upload();
});
}
}
The abstract class implementing base functionality of UploadWhenResolvedInterface :
<?php namespace App\Http\Files;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Contracts\Filesystem\Factory as Filesystem;
class UploadedFile implements UploadWhenResolvedInterface {
private $request;
private $filesystem;
public $path = '';
protected $disk = 'local';
protected $name = '';
public function __construct (Request $request, Filesystem $filesystem) {
$this->request = $request;
$this->filesystem = $filesystem;
}
public function upload () {
// Check existence of disk and file
if (! $disk = $this->getDisk()) throw new Exception("No disk");
if (! $file = $this->getFile()) throw new Exception("No file");
// Check valididty of the upload
if (! $file->isValid()) throw new Exception("File not valid");
// Get the uploaded file real path
$real_path = $file->getRealPath();
// Get a relative path from the disk's root and set it
$this->path = $this->getRelativePath(
$file->getClientOriginalName(),
$file->getClientOriginalExtension()
);
// Put the uploaded file content to the path
$disk->put($this->path, file_get_contents($real_path));
// Remove the uploaded file
unset($real_path);
}
public function getPath () {
return $this->path;
}
protected function getDisk () {
return $this->filesystem->disk($this->disk);
}
protected function getFile () {
// If a name is specified get the file named like this
if ($this->name) return $this->request->file($this->name);
// Otherwise get the first uploaded file
$keys = $this->request->files->keys();
return $this->request->file($keys[0]);
}
protected function getRelativePath ($file_name, $file_ext) {
// Generate a random file name and get some subdirs
$random = md5(str_random() . $file_name);
$parts = str_split($random, 2);
$subdirs = array_slice($parts, 0, 6);
// Get the relative path
$path = implode($subdirs, '/') . '/' . $random . '.' . $file_ext;
// Ensure a file with the same path doesn't already exists
if ($this->getDisk()->exists($path)) {
return $this->getRelativePath($file_name, $file_ext);
}
return $path;
}
}
A concrete uploaded file :
- filesystem disk name and file input name can be specified with the $disk and $name attributes. By default local disk and first uploaded file are used (see abstract class)
- getRelativePath can be overrided if you want to make a custom file path from the file name and ext. By default it md5 the filename + random string and append subdirectories (see abstract class)
- This file is not needed if you use local disk and your form only upload one file
<?php namespace App\Http\Files;
class UploadedPicture extends UploadedFile {
public $disk = 'pictures';
public $name = 'my_picture';
}
Then no more headaches at controller level :
Route::post('files', function (UploadedPicture $uploaded_picture) {
// The file from input 'my_picture' is uploaded in the 'pictures' disk with an unique path
echo $uploaded_picture->getPath();
});
I would gladly read your reviews and suggestions :)