Uploading Files

#Storing Files

There are various elements for uploading single files like avatar, file or image. What they have in common is that their files are being stored the same way on the database and filesystem level.

If you check out config/laraform.php config file you'll see that there's a property called 'store':

// config/laraform.php

return [
  // ...
  'store' => [
    'disk' => 'public',
    'folder' => 'uploads'
  ],
  // ...
]

By default the disk is set to public, which refers to the public disk in config/filesystems.php and the folder is an optional directory name which will be stored along with the filename. Let's see a few examples.

First, we have the default store setup pointing to public disk and using uploads folder in

// config/laraform.php

return [
  // ...
  'store' => [
    'disk' => 'public',
    'folder' => 'uploads'
  ],
  // ...
]
// config/filesystems.php

return [
  // ...
  'disks' => [
    'local' => [
      'driver' => 'local',
      'root' => storage_path('app'),
    ],
    'public' => [
      'driver' => 'local',
      'root' => storage_path('app/public'),
      'url' => env('APP_URL') . '/storage',
      'visibility' => 'public',
    ],
    's3' => [
      'driver' => 's3',
      'key' => env('AWS_KEY'),
      'secret' => env('AWS_SECRET'),
      'region' => env('AWS_REGION'),
      'bucket' => env('AWS_BUCKET'),
    ],
  ],
  // ...
]

This means that files will be stored in you storage/app/public/uploads directory because:

  • storage/app/public comes from config.filesystems.disks.public.root
  • uploads comes from config.laraform.folder.

The file therefore can be accessed via APP_URL/storage/uploads/filename.ext because:

  • APP_URL/storage comes from config.filesystems.disks.public.url
  • uploads/filename.ext is what being stored in the database (uploads/ being prepended to the filename on a database level).

Just as a quick note APP_URL/storage directory is not available to users because no storage directory exists in public folder. Files are being stored in storage/app/public which needs to be symlinked to public directory, which you can achieve that easily by running php artisan storage:link artisan command. After that described setup will work.

#Custom Store Location

In a next example we'll create a new filesystem disk, that anything uploaded via Laraform will use directly. Let's get started by creating a new disk called laraform:

// config/filesystems.php

return [
  // ...
  'disks' => [
    'local' => [
      'driver' => 'local',
      'root' => storage_path('app'),
    ],
    'public' => [
      'driver' => 'local',
      'root' => storage_path('app/public'),
      'url' => env('APP_URL') . '/storage',
      'visibility' => 'public',
    ],
    's3' => [
      'driver' => 's3',
      'key' => env('AWS_KEY'),
      'secret' => env('AWS_SECRET'),
      'region' => env('AWS_REGION'),
      'bucket' => env('AWS_BUCKET'),
    ],
    'laraform' => [
      'driver' => 'locale',
      'root' => public_path('uploads'),
      'url' => env('APP_URL') . '/uploads',
    ],
  ],
  // ...
]

As you can see we set the root path directly to public/uploads and the url to APP_URL/uploads. Now let's modify our Laraform store config to use this disk:

// config/laraform.php

return [
  // ...
  'store' => [
    'disk' => 'laraform',
    'folder' => null
  ],
  // ...
]

Now every file will be stored in public/uploads and can be accessed via APP_URL/uploads while only filenames will be stored in the database.

This is not a recommended approach, because it's better to store your files in the storage directory but it's only here to demonstrate that you are completely free to set up your storage location when using Laraform.

#Overriding Configuration

When you are using any element which involves file uploads, the files will be stored based on the disk and folder defined in the config. However you are free to define these two on an element level:

<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'id' => [
      'type' => 'key'
    ],
    'name' => [
      'type' => 'text',
      'label' => 'Name',
    ],
    'avatar' => [
      'type' => 'avatar',
      'label' => 'Avatar',
      'disk' => 'public',
      'folder' => 'avatars'
    ]
  ];
}

The users avatar pictures will be uploaded to the public disk into avatars folder and the avatars/filename.ext will be stored in the database.

#Custom Store Function

To go even further you can write your own storing function as an anonym function:

<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'id' => [
      'type' => 'key'
    ],
    'username' => [
      'type' => 'text',
      'label' => 'Username',
    ],
    'avatar' => [
      'type' => 'avatar',
      'label' => 'Avatar',
      'store' => function($file, $entity){
        // storing file

        return [
          // ...
        ]
      }
    ]
  ];
}

First you have the $file which is the Illuminate\Http\UploadedFile instance created from the uploaded file, then there's the $entity which is basically an Illuminate\Database\Eloquent\Model instance, in our case App\User.

Using this custom store function you have to take care of saving the file with the help of $file and $entity, and you must return the final filename which should be stored in the database along with any other data which supposed to be updated (eg. meta data, as we will see later).

Let's see how we would finish our custom store function in order to store the filename in avatars/id-username.ext format:

<?php

namespace App\Forms;

use Laraform;
use App\User;
use Illuminate\Support\Str;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'id' => [
      'type' => 'key'
    ],
    'username' => [
      'type' => 'text',
      'label' => 'Username',
    ],
    'avatar' => [
      'type' => 'avatar',
      'label' => 'Avatar',
      'folder' => 'avatars',
      'disk' => 'public',
      'store' => function($file, $entity){
        $folder = 'avatars';
        $filename = $entity->id . '-' . Str::slug($entity->username) . '.' . $file->extension();

        $file->storeAs(
          $folder,
          $filename,
          ['disk' => 'public']
        );

        return [
          'avatar' => $folder . '/' . $filename
        ];
      }
    ]
  ];
}

#Storing Meta Data

Let's suppose we not only intend to store the filenames in our database but also some information about them. These typically include original filename, file size, mime type, etc. and can be extracted from the files, once uploaded.

To store them you simply need to instruct Laraform to do so by using store${Meta} option:

<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'avatar' => [
      'type' => 'avatar',
      'label' => 'Avatar',
      'storeSize' => 'avatar_size',
    ]
  ];
}

This will append a meta element to the form and store the file size in it.

In fact you can also create an element that storeSize refers to and Laraform, instead of adding a meta element will store the size in that field:

<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'avatar' => [
      'type' => 'avatar',
      'label' => 'Avatar',
      'storeSize' => 'avatar_size',
    ],
    'avatar_size' => [
      'type' => 'text',
      'label' => 'File size'
    ]
  ];
}

The following meta options are provided within Laraform by default:

  • storeSize - file size
  • storeMime - mime type
  • storeOriginalName - original name
  • storeExtension - extension.

#Storing Meta With Custom Store Function

When you are using custom store function you can't rely on Laraform's default meta storing functionality. Instead, you have to take care of adding your own meta elements to the form and return data form them from the store function. Let's see that by action:

Sticking with our example from custom store function we'll now add additional meta tags that will be saved along with the avatar image:

<?php

namespace App\Forms;

use Laraform;
use App\User;
use Illuminate\Support\Str;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'id' => [
      'type' => 'key'
    ],
    'username' => [
      'type' => 'text',
      'label' => 'Username',
    ],
    'avatar' => [
      'type' => 'avatar',
      'label' => 'Avatar',
      'folder' => 'avatars',
      'disk' => 'public',
      'store' => function($file, $entity){
        $folder = 'avatars';
        $filename = $entity->id . '-' . Str::slug($entity->username) . '.' . $file->extension();

        $file->storeAs(
          $folder,
          $filename,
          ['disk' => 'public']
        );

        return [
          'avatar' => $folder . '/' . $filename,
          'avatar_size' => $file->getClientSize(),
          'avatar_mime' => $file->getClientMimeType(),
          'avatar_original_name' => $file->getClientOriginalName(),
          'avatar_extension' => $file->getClientOriginalExtension(),
        ]
      }
    ],
    'avatar_size' => [
      'type' => 'meta'
    ],
    'avatar_mime' => [
      'type' => 'meta'
    ],
    'avatar_original_name' => [
      'type' => 'meta'
    ],
    'avatar_extension' => [
      'type' => 'meta'
    ]
  ];
}

#Deleting Files

By default when you remove a file and save the changes the files will not only be removed from the database but also physically.

If you wish to turn off this feature you can set the prunable option of the element to false:

<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'avatar' => [
      'type' => 'avatar',
      'label' => 'Avatar',
      'prunable' => false
    ]
  ];
}

#Custom Delete Function

To implement a custom deletion logic you can use delete option of the element. Here's how you can perform the default deletion on your own:

<?php

namespace App\Forms;

use Laraform;
use App\User;
use Illuminate\Support\Facades\Storage;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'avatar' => [
      'type' => 'avatar',
      'label' => 'Avatar',
      'disk' => 'public',
      'delete' => function($path, $entity){
        Storage::disk('public')->delete($path);
      }
    ]
  ];
}

The $path is the path to the filename within storage disk root and the $entity is the Illuminate\Database\Eloquent\Model instance, in our case App\User.

#Handling Multiple Files

There are certain elements, like gallery and multifile which allows uploading multiple files. These elements in fact are very similar to list element and can be used to store their data the same way. Let's see what we mean by that.

#Simple Array

First of all there is the simple usage of a multifile type element, which stores uploaded filenames in an array:

Form's `data`:

      
          
<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'documents' => [
      'type' => 'multifile',
      'label' => 'Documents'
    ]
  ];
}
          
        

On the backend User model you can store these documents as a JSON string:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function setDocumentsAttribute($value) {
        $json = json_encode($value);

        $this->attributes['documents'] = $json;
    }

    public function getDocumentsAttribute() {
        if (!isset($this->attributes['documents'])) {
          return null;
        }

        return json_decode($this->attributes['documents'], true);
    }
}

#Array Of Objects

Secondly, if you want to use relationships to store your files data or add meta data you can define a storeFile option which will transform the data structure into an array of objects, where the filename will be stored under the element specified as storeFile:

Form's `data`:

      
          
<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'documents' => [
      'type' => 'multifile',
      'label' => 'Documents',
      'storeFile' => 'file'
    ]
  ];
}
          
        

As you can see file is now stored within objects which enables us to store the data as a hasMany relationship:

<?php

// App/User.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function documents() {
        return $this->hasMany(Document::class);
    }
}
<?php

// App/Document.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Document extends Model
{
    public $fillable = ['file'];
}

Of course this still does allow you to store documents in a JSON string too the same way described above.

#Using Meta

Meta data can be added to multiple files also when you are using the storeFile option, because that way each file is an object.

To do that simply define store${Meta} options for the element, just like when using single files:

<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'documents' => [
      'type' => 'multifile',
      'label' => 'Documents',
      'storeFile' => 'file',
      'storeSize' => 'size'
    ]
  ];
}

This will also append a meta element to each file object and can be stored by adding size to the Document model:

<?php

// App/Document.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Document extends Model
{
    public $fillable = ['file', 'size'];
}

If you wish to define your own elements for containing meta data you can do that by adding them to fields option:

<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'documents' => [
      'type' => 'multifile',
      'label' => 'Documents',
      'storeFile' => 'file',
      'storeSize' => 'size',
      'fields' => [
        'size' => [
          'type' => 'text'
        ]
      ]
    ]
  ];
}
Please note that you can define any type of element in the fields option as Laraform will handle them as normal elements, but it's main purpose is to store meta data. Therefore defining any other type of element than meta may result in unintended side effects as it's not a supported feature of Laraform at this stage. If you do so make sure you use it with caution!

#Custom Store Function

Writing a custom store function for multiple file upload elements is exactly as writing for single file uploaders:

<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'documents' => [
      'type' => 'multifile',
      'label' => 'Documents',
      'store' => function($file, $entity){
        // define store logic here

        return [
          // return file data
        ];
      }
    ]
  ];
}

#Custom Delete Function

Just like writing custom store function creating custom delete functions are just the same as for single file uploaders:

<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'documents' => [
      'type' => 'multifile',
      'label' => 'Documents',
      'delete' => function($path, $entity){
        // define deletion logic here
      }
    ]
  ];
}

#Validating File

When you define rules option for an element the validation rules are applied to the element where they are defined. Because of this rules will be applied for the multiple file upload element and validation rules like max will limit the number of files that can be uploaded but not the file size.

To apply rules for the uploaded files, use fileRules option:

<?php

namespace App\Forms;

use Laraform;
use App\User;

class UserForm extends Laraform
{
  public $model = User::class;

  public $schema = [
    'documents' => [
      'type' => 'multifile',
      'label' => 'Documents',

      // Maximum 2 files can be uploaded.
      'rules' => 'max:2',

      // File can be maximum 1 MB each.
      'fileRules' => 'max:1024',
    ]
  ];
}