Search code examples
laravellaravel-5.8laravel-filesystem

Custom storage path per model


I'm implementing an email handling system where I want to save the original email and all its attachments to a path. For example - mail-data/123456/5

Where 123456 is the parent ID and 5 is the child ID.

In filesystems.php I've created a custom disk called mail-data

'mail-data' => [
    'driver' => 'local',
    'root' => storage_path('app/public/mail-data'),
    'visibility' => 'private',
],

This works great as far as setting a prefix for the storage path, visibility etc. However, what I want to be able to do is on a per model basis, call a storage property and it return the mail-data driver set to the exact path. This way, all of my logic can simply be:

$model->storage->put($file->getFilename(), $file->stream());

rather than:

$path = Storage::disk('mail-data')->put($model->parent_id . '/' . $model->id . '/' . $file->getFilename(), $file->getStream())

I think the best way to do this by creating an accessor on the model, and I've been able to update the adapter, I just don't know how to update that on the Filesystem instance and return it?

public function getStorageAttribute()
{
    $storage = Storage::disk('mail-data');

    $adapter = $storage->getAdapter();

    $adapter->setPathPrefix($adapter->getPathPrefix() . $this->parent_id . '/' . $this->id);

    // what to do here to return our modified storage instance?
}

Solution

  • Right, I was a bit stupid here... it turns out that when you run setPathPrefix on the adapter, it's all by reference so the code above actually had the desired effect. For anyone Googling in the future, here is the final code -

    On the model -

    /**
     * Get our storage disk for this model
     *
     * @return \Illuminate\Contracts\Filesystem\Filesystem
     */
    public function getStorageAttribute()
    {
        $storage = Storage::disk('mail-data');
    
        $adapter = $storage->getAdapter();
        $adapter->setPathPrefix($adapter->getPathPrefix() . $this->ticket_id . '/' . $this->id);
    
        return $storage;
    }
    

    I can then access my models storage at the absolute storage path simply using $model->storage. So my now much cleaner code for saving my mail data looks like this (no more calculating paths and having to worry about calculating paths anywhere else in my logic) -

    $storage = $model->storage;
    
    $storage->put('email.eml', $mail->message()->getStream());
    
    /** @var MimePart $attachment */
    foreach ($mail->attachments() as $attachment) {
        $storage->put($attachment->getFilename(), $attachment->getStream());
    }
    

    Very happy with that solution and I hope it comes in handy for someone else in the future :)