Silver Comet Logo

Uploading images using Backpack's fakeColumn mutators

Written 25th May 2023


Introduction

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” and upload 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.

I've got the skills to help

just let me know how

I've been in the industry professionally for more than 10 years, and developing for over 25. I've made APIs for thousands of users and software for some of the UK's biggest brands.