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':
/*
|--------------------------------------------------------------------------
| Store
|--------------------------------------------------------------------------
|
| Default location to store uploaded files.
|
*/
'store' => [
    'disk' => 'public',
    'folder' => 'files'
],
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
/*
|--------------------------------------------------------------------------
| Store
|--------------------------------------------------------------------------
|
| Default location to store uploaded files.
|
*/
'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/publiccomes fromconfig.filesystems.disks.public.rootuploadscomes fromconfig.laraform.folder.
The file therefore can be accessed via APP_URL/storage/uploads/filename.ext because:
APP_URL/storagecomes fromconfig.filesystems.disks.public.urluploads/filename.extis what being stored in the database (uploads/being prepended to the filename on a database level).
Just as a quick note
APP_URL/storagedirectory is not available to users because nostoragedirectory exists inpublicfolder. Files are being stored instorage/app/publicwhich needs to be symlinked topublicdirectory, which you can achieve that easily by runningphp artisan storage:linkartisan 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:
/*
|--------------------------------------------------------------------------
| Store
|--------------------------------------------------------------------------
|
| Default location to store uploaded files.
|
*/
'store' => [
    'disk' => 'public',
    '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
storagedirectory 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.
id field is required in every case when you are using files within your forms.#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 = [
    'id' => [
      'type' => 'key',
    ],
    '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 = [
    'id' => [
      'type' => 'key',
    ],
    '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 sizestoreMime- mime typestoreOriginalName- original namestoreExtension- 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 = [
    'id' => [
      'type' => 'key',
    ],
    '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 = [
    'id' => [
      'type' => 'key',
    ],
    '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 = [
    'id' => [
      'type' => 'key',
    ],
    '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 = [
    'id' => [
      'type' => 'key',
    ],
    'documents' => [
      'type' => 'multifile',
      'label' => 'Documents',
      'storeFile' => 'file',
      'storeSize' => 'size',
      'fields' => [
        'size' => [
          'type' => 'text'
        ]
      ]
    ]
  ];
}
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 = [
    'id' => [
      'type' => 'key',
    ],
    '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 = [
    'id' => [
      'type' => 'key',
    ],
    '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 = [
    'id' => [
      'type' => 'key',
    ],
    'documents' => [
      'type' => 'multifile',
      'label' => 'Documents',
      // Maximum 2 files can be uploaded.
      'rules' => 'max:2',
      // File can be maximum 1 MB each.
      'fileRules' => 'max:1024',
    ]
  ];
}