Laravel 11.x + Backpack + Article with image (file upload)
In order to proceed you should have:
- Laravel installed + Backpack admin (teaching purposes)
- Tags CRUD created
- Article CRUD created
Note: There is an older version of this tutorial before Backpack 11.x
#STEP 1: Create migration to add an image to an article
php artisan make:migration add_image_to_articles_table --table=articles
edit the file so it looks like this
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('articles', function (Blueprint $table) {
$table->string('image',255)->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('articles', function (Blueprint $table) {
$table->dropColumn('image');
});
}
};
We are adding a new column to the articles table. In the down method, we are removing the column. The new column will hold information about the image name.
#STEP 2: Run migration
php artisan migrate
The expected end result of running the query is adding a new column to the tables.
#STEP 3: Alter the Articles model
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
We are going to use Str support and facade for the Storage. We need to be able to delete the uploaded image once:
- new image is added
- the entity is deleted
public static function boot()
{
parent::boot();
static::deleting(function($obj) {
if ($obj->image) {
// Remove 'storage/' from the start of the path to get the relative path
$path = str_replace('storage/', '', $obj->image);
$response = Storage::disk('public')->delete($path);
}
});
}
private function deleteImage($attribute_name) {
// Remove 'storage/' from the start of the path to get the relative path
$path = str_replace('storage/', '', $this->{$attribute_name});
$response = Storage::disk('public')->delete($path);
}
public function setImageAttribute($value)
{
$attribute_name = "image";
// destination path relative to the disk above
$destination_path = "articles";
// if the image was erased
if ($value==null) {
$this->deleteImage($attribute_name);
// set null in the database column
$this->attributes[$attribute_name] = null;
} else {
$this->deleteImage($attribute_name);
$disk = "public";
// filename is generated - md5($file->getClientOriginalName().random_int(1, 9999).time()).'.'.$file->getClientOriginalExtension()
$this->uploadFileToDisk($value, $attribute_name, $disk, $destination_path, $fileName = null);
$this->attributes[$attribute_name] = 'storage/' . $this->attributes[$attribute_name];
}
}
The first function will make sure that once we delete an entry — the file will be deleted as well.
The second function is a mutator that is responsible for uploading the image to the correct folder.
Note: We need to delete the old entry (image) in two cases. Once when the image is removed or when the image is replaced by another image.
#STEP 4: Adjust Articles controller
Add the following code
[
'label' => "Article Image",
'name' => "image",
'type' => ($show ? 'view' : 'upload'),
'view' => 'partials/image',
'upload' => true,
]
to the getFieldsData function
and also adjust setupListOperation function
/**
* Define what happens when the List operation is loaded.
*
* @see https://backpackforlaravel.com/docs/crud-operation-list-entries
* @return void
*/
protected function setupListOperation()
{
$this->crud->set('show.setFromDb', false);
$this->crud->addColumns($this->getFieldsData(TRUE));
/**
* Columns can be defined using the fluent syntax or array syntax:
* - CRUD::column('price')->type('number');
* - CRUD::addColumn(['name' => 'price', 'type' => 'number']);
*/
}
#STEP 5: Create new view for the image
Create a new view file in /resources/views/partials/image.blade.php
Content of the view file:
@if(isset($entry->image))
<img src="/{{ $entry->image }}" width="120" alt="Article image" />
@endif
#STEP 6: Make the disk public and allow files to be shown
php artisan storage:link
Test everything