Whilst trying out Laravel Backpack I didn't want to commit to purchasing until I knew it was right for me. I hit quite a niche problem which was that I wanted to upload images inside the PageManager add on and I couldn't get the mutator to work with the fakeColumns. It should be noted that in the last few days I've made a pull request which was accepted allowing for better documentation of this aspect so hopefully it isn't a problem going forward. I'd like to thank the maintainers of Backpack for their positive attitude and approach to community contributions.
Disclaimer
I’m not guaranteeing this is the “right” way to do this, simply that after much trial and error this was the solution that worked for me. There’s already numerous improvements I can see, however I wanted to share this to help anyone else who might be working with BackPack’s upload system and struggling to upload as expected.
Database Migration
First we need to create a new column if it doesn’t exist. In our example we’re creating a new column called images on the pages table created by https://github.com/Laravel-Backpack/PageManager
php artisan make:migration add_images_to_pages_table
This creates a new file under /database/migrations/
which we need to update to look like the one below.
<?php
use Illuminate\\Database\\Migrations\\Migration;
use Illuminate\\Database\\Schema\\Blueprint;
use Illuminate\\Support\\Facades\\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('pages', function (Blueprint $table) {
$table->longText('images')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('pages', function (Blueprint $table) {
$table->dropColumn('images');
});
}
};
Breaking this down, Backpack uses fake columns by adding a serialised string so our column is of type “longText” and is nullable in case there’s no upload.
PageTemplates
Open /app/PageTemplates.php
and update the page template you’re using with the following:
$this->crud->addField([
'name' => 'first_image',
'label' => 'First image',
'type' => 'upload',
'upload' => true,
'store_in' => 'images', // must be the fakeColumn name
'fake' => true,
]);
Here what we’re doing is setting:
name
as “first_image” (personal preference, I don’t like names which might be repeated).type
to “uploaded” andupload
to “true”store_in
as our fakeColumn name.fake
to true
Model
Publishing
If you don’t have the /app/Models/Page.php
model then you’ll need to run:
php artisan vendor:publish --model="Backpack\\PageManager\\app\\Models\\Page"
Variables
Next we need to update our model. Open /app/Models/Page.php
and find the $fillable
, $casts
and $fakeColumns
variables adding images
as a value.
Your arrays should look like this when finished:
protected $fillable = ['template', 'name', 'title', 'slug', 'content', 'extras', 'images'];
protected $fakeColumns = ['extras', 'images'];
protected $casts = ['extras' => 'array', 'images' => 'array'];
$fillable
ensures Laravel knows we want to allow data to be stored in this column$fakeColumns
means we might be storing more than 1 value per column$casts
means we want the data to come out as an array in case there is more than 1 value.
Mutator
Next we need to create a mutator against the FakeColumn name, so in our case Images.
public function setImagesAttribute($value)
{
dd($value); // Remove once you've verified the mutator is accessed!
foreach ($value as $key => $file) {
if ($file instanceof \\Illuminate\\Http\\UploadedFile) {
$stored[$key] = $file->storePubliclyAs('public/images/pages', $file->getClientOriginalName());
}
}
if (!empty($stored)) {
$this->attributes['images'] = json_encode($stored);
}
}
The $value
argument is going to be an array of objects in our case. If doing this with the Extras column you would now need to run a foreach and check for the object type or name to exclude the elements you don’t want to change.
In our example we’ve created a foreach
within which we’re checking that the upload is correctly an instance of \\Illuminate\\Http\\UploadedFile
if it is then we’re uploading it to the public/images/pages
storage path using the original file name. We’re then adding it to an array and json_encoding the finished array.
💡 Backpack does a lot of the leg work for us by uploading the file as an Illuminate\Http\UploadedFile object (https://laravel.com/api/9.x/Illuminate/Http/UploadedFile.html)
Testing
It’s probably worth checking the mutator is firing at this stage. You’ll notice we left a dd() function in the mutator. If you go to your admin panel and upload an image in your new field, you should be presented with something that looks like:
array:1 [▼ // app/Models/Page.php:99
"first_image" => Illuminate\Http\UploadedFile {#1540 ▼
-test: false
-originalName: "pricing1.svg"
-mimeType: "image/svg+xml"
-error: 0
#hashName: null
path: "/private/var/tmp"
filename: "php7FKaxT"
basename: "php7FKaxT"
pathname: "/private/var/tmp/php7FKaxT"
extension: ""
realPath: "/private/var/tmp/php7FKaxT"
aTime: 2023-05-25 10:46:41
mTime: 2023-05-25 10:46:41
cTime: 2023-05-25 10:46:41
inode: 10641977
size: 12936
perms: 0100600
owner: 501
group: 0
type: "file"
writable: true
readable: true
executable: false
file: true
dir: false
link: false
}
]
Now you can remove our dd()
inside the mutator and enjoy your new field.