A large part of our Wallpaper website involves us, unsurprisingly, working with images.

Whether you're working with images, PDFs, audios, movies, or any of the other types of file, the common thing is that they all need some way of being uploaded.

Previously I mentioned how tended to use a database GUI client as my "admin panel" for as long as I could get away with. When it's just you working on a project, maybe working with a database client is an acceptable alternative to a full-blown admin dashboard.

Likewise, if it's just you working on your project, maybe using some file transfer system is an acceptable way of uploading images.

However, sooner or later you will likely want to make your site that little bit nicer to work with, and implement a way to upload new Wallpapers from the back end.

Before we go further though - this is development. There are - in most cases - more than one way to fix a particular problem.

File uploads, or in our case, uploading Images is a very common problem.

As such, smart developers congregate together and come up with solutions to these problems.

I'm all for using these solutions but only if I understand - in some capacity - how they operate beneath the surface.

See, the thing is - from my experience - things like this become very frequently used parts of my applications. After all, frequently I need to upload images to keep my site fresh, and if one way is clunky, and another is streamlined, I'm going to not only do the streamlined one, but I'm likely going to start doing it more often.

Which will mean this code is going to get hammered.

Which almost certainly will mean more bugs are noticed, and they will need fixing.

I don't mind fixing bugs. It is a form of coding, if not the most enjoyable. And I love coding. Any excuse, and all that.

But the thing is, if I don't really know what this code is doing, then fixing those bugs is going to take longer and make me feel frustrated. I'd rather be working on the more interesting new features.

Why I'm telling you this is because there already exists a bundle which largely takes care of this process for you. It's called VichUploaderBundle.

EasyAdminBundle docs come with a guide to integrate with VichUploaderBundle.

We are not going to use this bundle.

We are going DIY.

Form A Line

We need to allow ourselves, and other admin users to post in (Create) new wallpapers.

This needs to include the slug, the width and the height, stuff like that.

Now, in truth, getting our user's to do most of this donkey work is pointless as this info can be determined in code.

This is an improvement we will make later.

For now, we need a rudimentary working system.

At this point we can boil this down to a form.

How the data gets into that form is not our concern. It might be from a phone. It might be from your car's dashboard. It might be from some front end code we write (hint: very likely this one).

If we aren't doing anything out of the ordinary, we can make use of some further easy_admin config to create a form for us.

We need to tell it what fields we have, and it will take care of the rest.

There are ways we can hook into this process.

We could override this method with our own implementation. We don't need to do any of that.

Our circumstances are normal for what this bundle expects.

# /app/config/config/easy_admin_bundle.yml

easy_admin:
    entities:
        Category:
            class: AppBundle\Entity\Category
        Wallpaper:
            class: AppBundle\Entity\Wallpaper
            list:
                fields:
                    - 'id'
                    - 'filename'
                    - 'slug'
                    - 'width'
                    - 'height'
                    - { property: 'image', type: 'image', base_path: '/images/' }
            form:
                fields:
                    - 'slug'
                    - 'width'
                    - 'height'

Note here that form comes under Wallpaper, not under list - indentation can be a source of bugs.

When we go to create a new Wallpaper now, we see only three fields:

  • slug
  • width
  • height

Trying to submit this form will result in a SQL constraint violation.

An exception occurred while executing 'INSERT INTO wallpaper (filename, slug, width, height, category_id) VALUES (?, ?, ?, ?, ?)' with params [null, "some-slug", 1920, 1080, null]:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'filename' cannot be null
500 Internal Server Error - NotNullConstraintViolationException
2 linked Exceptions: PDOException » PDOException »

We could add in a filename form field, and expect the user to provide this information. This isn't a bad way to test that this component behaves as expected.

It would be nicer to have a facility on the form which allowed the user to directly upload a new file.

Let's go at this top down.

To allow a user to upload a file from a HTML web page, we need to display an <input type="file"/> on a form. Likely we will have more properties etc, but that's the gist of it.

To do this if we were using our own Symfony form setup is a process you may or may not be familiar with. It's one of earliest Cookbook entries I remember reading - quite a few times over as I recall :)

We won't be setting up a bunch of new form types here though.

Instead we will provide our forms as YAML configuration.

We can easily create a form with an <input type="file"/> under list:

# /app/config/config/easy_admin_bundle.yml

easy_admin:
    entities:
        Category:
            class: AppBundle\Entity\Category
        Wallpaper:
            class: AppBundle\Entity\Wallpaper
            list:
                fields:
                    - 'id'
                    - 'filename'
                    - 'slug'
                    - 'width'
                    - 'height'
                    - { property: 'image', type: 'image', base_path: '/images/' }
            form:
                fields:
                    # new line below
                    - { property: 'file', type: 'file', label: 'File' }
                    - 'slug'
                    - 'width'
                    - 'height'

This introduces a new concept. Similar to when we added the image property to our list view, here we have just invented this concept of a file without any code to make it work.

The file property isn't on our Wallpaper yet. We need to add it.

    /**
     * @ORM\Column(type="string")
     */
    private $file;

    // also generate getters and setters

How we get to an @ORM\Column(type="string") is going to be more involved than if we were posting in a plain old text field.

The process will be this:

We will have a form that accepts submissions that contain files.

When the form is submitted, one of the fields (the file field) will contain the file data that is going to get set onto the Wallpaper entity.

Other stuff might happen here courtesy of events we may not directly control. This happens all the time in Symfony, btw.

We need to take ownership of this file data.

As part of the upload process, PHP will have stored off our file for us in a directory under its own control.

We will need to move this file from the directory that PHP has temporarily stored it in, into the web/images directory. Of course, this directory can be any other directory you need to use.

Following the guidelines set out in the file upload Cookbook entry, we will do this process using a Doctrine lifecycle callback.

When it comes time to save this entity off to the database, only then will we move the file from wherever that PHP is temporarily storing it, into a path on disk that we control. In our case this path will be:

"%kernel.root_dir%/../web/images/"

If you haven't yet seen, we covered both kernel.root_dir, and it's new-in-Symfony 3.3 more friendly brother, kernel.project_dir previously.

As part of this process we can set other properties - such as the filename - dynamically.

We're about to do a bunch of work here that could become a late night, stressed out headache if we don't get it right. Whenever I hit code like this, I bust out the unit tests.

In order to test this process I'm going to use PhpSpec.

composer require --dev phpspec/phpspec

Also be sure to update composer.json with the required autoload setting:

"autoload": {
    "psr-0": {
        "": "src/"
    }
}

Official installation docs.

Fortunately that's us done with installing PhpSpec. Nice and easy.


Code For This Course

Get the code for this course.

Share This Episode

If you have found this video helpful, please consider sharing. I really appreciate it.


Episodes in this series

# Title Duration
1 Introduction and Site Demo 02:14
2 Setup and a Basic Wallpaper Gallery 08:43
3 Pagination 08:24
4 Adding a Detail View 04:47
5 Creating a Home Page 11:14
6 Creating our Wallpaper Entity 07:50
7 Wallpaper Setup Command - Part 1 - Symfony Commands As a Service 05:56
8 Wallpaper Setup Command - Part 2 - Injection Is Easy 08:53
9 Wallpaper Setup Command - Part 3 - Doing It With Style 05:37
10 Doctrine Fixtures - Part 1 - Setup and Category Entity Creation 08:52
11 Doctrine Fixtures - Part 2 - Relating Wallpapers with Categories 05:56
12 EasyAdminBundle - Setup and Category Configuration 06:02
13 EasyAdminBundle - Wallpaper Setup and List View 07:46
14 EasyAdminBundle - Starting with Wallpaper Uploads 05:57
15 Testing with PhpSpec to Guide Our Implementation 03:39
16 Using PhpSpec to Test our FileMover 05:34
17 Symfony Dependency Testing with PhpSpec 08:47
18 Defensive Counter Measures 06:32
19 No Tests - Part 1 - Uploading Files in EasyAdminBundle 11:02
20 No Tests - Part 2 - Uploading Files in EasyAdminBundle 07:05
21 Don't Mock What You Don't Own 09:36
22 You've Got To Take The Power Back 07:36
23 Making Symfony Work For Us 08:56
24 Testing The Wallpaper File Path Helper 15:11
25 Finally, It Works! 14:56
26 Why I Prefer Not To Use Twig 16:50
27 Fixing The Fixtures 11:20
28 Untested Updates 14:30
29 Untested Updates Part Two - Now We Can Actually Update 06:33