# Sharp built-in solution for uploads
Uploads are painful.
Sharp provide a very opinionated and totally optional solution to handle if you are using Eloquent and the WithSharpFormEloquentUpdater
trait (see related documentation).
The proposal is to use a special Sharp Model for all your uploads, and to link them to your Models with Eloquent's Morph relationships.
# Use SharpUploadModel
The base Model class is Code16\Sharp\Form\Eloquent\Uploads\SharpUploadModel
. Just create your own Model class and make it extends this base class.
You'll have to define the Eloquent $table
attribute to indicate the table name. So for instance, let's say your Model name choice is Media
, here's the class code:
use Code16\Sharp\Form\Eloquent\Uploads\SharpUploadModel;
class Media extends SharpUploadModel
{
protected $table = "medias";
}
# Generator
php artisan sharp:make:media <model_name> --table=<table_name>
# Create the migration
Sharp provides an artisan command for that:
sharp:create_uploads_migration <table_name>
Pass your specific table name in the table_name
argument ("medias" in our example).
This command will create a migration file like this one:
class CreateMediasTable extends Migration
{
public function up()
{
Schema::create('medias', function (Blueprint $table) {
$table->increments('id');
$table->morphs('model');
$table->string('model_key')->nullable();
$table->string('file_name')->nullable();
$table->string('mime_type')->nullable();
$table->string('disk')->default('local')->nullable();
$table->unsignedInteger('size')->nullable();
$table->text('custom_properties')->nullable();
$table->unsignedInteger('order')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('medias');
}
}
# Link to your Models
Now, you need to define the relationships. Let's say you have a Book model, and you want the user to be able to upload its cover and PDF version.
class Book extends Model
{
public function cover()
{
return $this->morphOne(Media::class, "model")
->where("model_key", "cover");
}
public function pdf()
{
return $this->morphOne(Media::class, "model")
->where("model_key", "pdf");
}
}
# Use it!
Let's pretend you already have data in this new table, here how to handle it.
# Properties
By default, you can get the file_name
, but also mime_type
and file's size
.
# Custom properties
You can add whatever property you need through custom properties, by setting it:
$book->cover->author = "Tomi Ungerer";
Custom properties will be stored in the custom_properties
column, as JSON.
You can retrieve the value the same way:
$author = $book->cover->author;
# Thumbnails
Thumbnail creation, for image, is built-in, with this function:
thumbnail($width=null, $height=null, $filters=[]);
You must first define the thumbnail directory, in Sharp's config:
// config/sharp.php
"uploads" => [
"thumbnails_dir" => "thumbnails",
],
This path is relative to the public
directory.
Then you can call $thumb = $book->cover->thumbnail(150)
to have a full URL to a 150px (width) thumbnail.
# Filters
The third argument is for Filters. For now, only two are available:
greyscale
->thumbnail(150, null, ["greyscale" => []])
fit: this one has 2 params,
w
for width andh
for height, and will center-fit the image in those constraints.
->thumbnail(150, null, ["fit" => ["w"=>150, "h"=>100]])
But of course you can provide here a custom one. You'll need for that to first create a Filter class that extends Code16\Sharp\Form\Eloquent\Uploads\Thumbnails\ThumbnailFilter
, implementing:
function applyFilter(Intervention\Image\Image $image)
: apply you filter, using the great Intervention API.function resized()
: (optional, default to false) Return true if the resize is part of theapplyFilter()
code.
Once the class is created, pass the full class path as filter name:
return $this->thumbnail($size, $size, [
CustomThumbnailFilter::class => ['w' => $w, 'fill' => '#ffffff']
]);
# Update with Sharp
The best part is this: Sharp will take care of everything related to update and store.
First declare your upload, like usual:
function buildFormFields()
{
$this->addField(
SharpFormUploadField::make("cover")
->setLabel("Cover")
->setFileFilterImages()
->setCropRatio("1:1")
->setStorageDisk("local")
->setStorageBasePath("data/Books")
);
}
Then add a customTransformer:
function find($id): array
{
return $this
->setCustomTransformer(
"cover",
new SharpUploadModelFormAttributeTransformer()
)
->transform(
Book::with("cover")->findOrFail($id)
);
}
The full path of this transformer is Code16\Sharp\Form\Eloquent\Transformers\SharpUploadModelFormAttributeTransformer
.
And finally, and this is a sad exception to the "don't touch the applicative code for Sharp", add this in your Model that declares an upload relationship (Book, in our example):
public function getDefaultAttributesFor($attribute)
{
return in_array($attribute, ["cover"])
? ["model_key" => $attribute]
: [];
}
This will tell SharpEloquentUpdater to add the necessary model_key
attribute when creating a new upload.
And... voilà! From there, Sharp will handle the rest.
# Updating custom attributes
So we want to add an author
custom attribute to our cover field: for this we add the field in the Sharp Entity Form, using the :
separator to designate a related attribute:
$this->addField(
SharpFormTextField::make("cover:author")
->setLabel("Author")
);
Here we intend to update the author
attribute of the cover
relation.
# What about upload lists?
So let's say we want to add pictures of inner pages, for our Book. It can be easily done by creating a morphMany
relation in the Book Model:
public function pictures()
{
return $this->morphMany(Media::class, "model")
->where("model_key", "pictures")
->orderBy("order");
}
And then add the field in the Sharp Entity Form:
$this->addField(
SharpFormListField::make("pictures")
->setLabel("Additional pictures")
->setAddable()->setAddText("Add a picture")
->setRemovable()
->setSortable()
->setOrderAttribute("order")
->addItemField(
SharpFormUploadField::make("file")
->setFileFilterImages()
->setStorageDisk("local")
->setStorageBasePath("data/Books/Pictures")
)
);
Note that we use the a special file
key for the SharpFormUploadField in the item.
You'll have next to update your Model special getDefaultAttributesFor()
function:
public function getDefaultAttributesFor($attribute)
{
return in_array($attribute, ["cover","pictures"])
? ["model_key" => $attribute]
: [];
}
All set.
# Updating custom attributes in upload lists
$this->addField(
SharpFormListField::make("pictures")
[...]
->addItemField(
SharpFormUploadField::make("file")
)->addItemField(
SharpFormTextField::make("legend")
)
);
In this code, the legend
designates a custom attribute.