I’m migrating a legacy PHP application to Symfony, and that includes implementing file uploads to AWS S3. Symfony does not provide this feature out of the box, and many of the existing tutorials are outdated or do not work well, therefore I decided to document this in the hope that it will be useful to someone else.
To implement this, we will need to install four packages:
aws/aws-sdk-php: the AWS PHP SDK.
oneup/flysystem-bundle: a bundle for Flysystem (league/flysystem), a filesystem abstraction library.
league/flysystem-aws-s3-v3: a Flysystem adapter for AWS SDK.
vich/uploader-bundle: a bundle that eases file uploads that are attached to ORM entities.
These packages can be easily installed with Composer:
Next, we need to add to AppKernel.php our newly installed bundles:
Now, we will start setting up the bundles on the configuration file app/config.yml.
First, we will setup the Flysystem bundle. This bundle will expose a set of adapters and filesystems for the application to use. The configuration below creates an adapter called assets_adapter and a filesystem called assets_fs, which in turn uses the adapter assets_adapter. The built-in awss3v3 Flysystem adapter takes two arguments: a service that’s nothing more than an instance of the AWS S3 SDK and the AWS bucket name (in this case, from the parameters.yml variable called assets_bucket).
Next, we will setup the VichUploaderBundle. The configuration below tells VichUploaderBundle to use the orm driver, which integrates with Doctrine, and to use the Flysystem storage adapter. It also creates a mapping, which will be hooked into our entity. A mapping tells VichUploaderBundle where to upload a file, how to generate its URI, and what naming strategy to use. In this case, we are supplying a user defined URI prefix, which is the S3 bucket URI (it’s defined in parameters.yml); we are telling it to upload the file the the assets_fs filesystem; and we are telling it to use the naming strategy that generates unique IDs for each file.
Finally, on app/services.yml, we will create the AWS S3 service that will be used to upload the files to S3. The configuration below defines a service named app.assets.s3 that is an instance of the Aws\S3\S3Client class, which takes the arguments defined below. These arguments are best stored in the parameters.yml file.
Good! Now it’s time to plug everything into our model.
Let us suppose we have an entity called Product, which represents a… product, ta-da! First, we will start by adding the @Vich\Uploadable annotation to the class, which allows it to upload the files. Then, we will add two properties: $image, which represents an image that was uploaded, and $imageFile, which represents a image that may be uploaded. Finally, we will also add a few setters and getters. The complete class can be seen below:
Note that the $imageFile property has the following annotation:
This annotation tells VichUploaderBundle to upload the file using the assets mapping we defined earlier, and to use the $image property to hold the details of the uploaded file.
Now, how do we upload an image? We add an image field to the product creation form. VichUploaderBundle provides a special form type VichImageType that automatically handles the file download, file preview and deletion. We simply add it to our form and use the $imageFile property - remember, this is the property that handles the image upload.
What about our controller? What do we need to change? Fortunately, we don’t need to change anything: VichUploaderBundle is well implemented, and uploading is done behind the scenes when the forms are processed.
That’s all. You’re ready to start uploading. ;)
You probably do not want to upload your files to S3 on your development and test environments (maybe you do, but that’s not very smart, lol). You can get over this by using a local filesystem on dev and a in-memory filesystem on test.