I think of Flysystem
as an interface to a disk (or other storage mechanism) and nothing more. Just as I would not ask my local filesystem to calculate a public URI, I would not ask Flysystem
to do it either.
I create objects that correspond to the files that I save via Flysystem
. Depending on my needs, I might save the public URI directly in the database record, or I might create a custom getter that builds the public URI based on runtime circumstances.
With Flysystem
, I know the path to the file when I write the file. In order to keep track of these files I'll typically create an object that represents a saved file:
class SavedFile extends Model
{
protected $fillable = [
'disk',
'path',
'publicURI',
];
}
When I save the file, I create a record of it in the database:
$disk = 'local_example';
$path = '/path/to/file.txt';
$contents = 'lorem ipsum';
$host = 'https://example.com/path/to/storage/root/';
if (Store::disk($disk)->put($path, $contents)) {
SavedFile::create([
'disk' => $disk,
'path' => $path,
'publicURI' => $host . $path,
]);
}
Whenever I need the public URI, I can just grab it off the SavedFile
model. This is handy for trivial applications, but it breaks down if I ever need to switch storage providers.
Leverage Inheritance
Another neat trick is to define a method that will resolve the public URI based on a variable defined in the child of an abstract SavedFile
model. That way I'm not hard-coding the URI in the database, and I can create new classes for other storage services with just a couple of variable definitions:
abstract class SavedFile extends Model
{
protected $table = 'saved_files';
protected $disk;
protected $host;
protected $fillable = [
'path',
];
public function getPublicURIAttribute()
{
return $this->host . $this->attributes['path'];
}
}
class LocalSavedFile extends SavedFile
{
$disk = 'local_example';
$host = 'https://example.com/path/to/storage/root';
}
class AwsSavedFile extends SavedFile
{
$disk = 'aws_example';
$host = 'https://s3.amazonaws.com/my_awesome_bucket';
}
Now if I have a bunch of files stored on my local filesystem and one day I move them to Amazon S3, I can simply copy the files and swap out the dependencies in my IoC binding definitions and I'm done. No need to do a time consuming and potentially hazardous find-and-replace on a massive database table, since the calculation of the URI is done by the model:
$localFile = LocalSavedFile::create([
'path' => '/path/to/file.txt'
]);
echo $localFile->publicURI; // https://example.com/path/to/storage/root/path/to/file.txt
$awsFile = AwsSavedFile::find($localFile->id);
echo $awsFile->publicURI; // https://s3.amazonaws.com/my_awesome_bucket/path/to/file.txt
Edit:
Support for Public and Private Files
Just add a flag to the object:
abstract class SavedFile extends Model
{
protected $table = 'saved_files';
protected $disk;
protected $host;
protected $fillable = [
'path',
'public',
];
public function getPublicURIAttribute()
{
if ($this->attributes['public'] === false) {
// you could throw an exception or return a default image if you prefer
return false;
}
return $this->host . $this->attributes['path'];
}
}
class LocalSavedFile extends SavedFile
{
$disk = 'local_example';
$host = 'https://example.com/path/to/storage/root';
}
$localFile = LocalSavedFile::create([
'path' => '/path/to/file.txt',
'public' => false,
]);
var_dump($localFile->publicURI); // bool(false)