Select Page

In the previous article I showed how to upload an image in Angular 2 and then make an HTTP request passing the image file as the content. In this article I will show how the API receives that image content.

This article assumes that you already have the Lumen framework setup and that you already have your routes and controllers in place.

So lets start by creating an action method. You would place this action method in the controller of your choice.

<?php
/**
 * @package App\Http\Controllers
 */
namespace App\Http\Controllers;

use App\Services\UploadService;
use Illuminate\Http\Response;
use Illuminate\Http\Request;
use Laravel\Lumen\Routing\Controller as BaseController;

/**
 * Upload Controller.
 * Receives image upload requests.
 *
 * @author silver.ibenye
 *
 */
class UploadController extends BaseController
{
    /**
     * @var Request
     */
    protected $request;

    /**
    * @var UploadService
    */
    private $service;

    /**
     *
     * @param Request $request
     * @param UploadService $uploadService
     * @param FileUploadRequestMapper $fileUploadRequestMapper
     */
    public function __construct(Request $request, UploadService $uploadService)
    {
        $this->request = $request;
        $this->service = $uploadService;
    }

    /**
     *
     * @return array
     */
    private function mapFileUploadRequest()
    {
        $request = [
                'file' => $this->request->getContent(),
                'contentType' => $this->request->header('Upload-Content-Type')
        ];
        return $request;
    }

    /**
     *
     * @param integer $userId
     * @return Reponse
     */
    public function uploadUserProfileImage()
    {
        $postRequest = $this->mapFileUploadRequest();

        $response = $this->service->uploadUserProfileImage($postRequest);

        return response($response, 200,
                [
                        'Content-Type' => 'application/json'
                ]);
    }
}

So we know from the previous article that the frontend passed in a custom header called Upload-Content-Type . You would notice in the action method shown above that it checks the Upload-Content-Type  header for the content type of the received image. And then it grabs the image itself from the request content, and then passes that to the service class.

<?php
/**
 * @package App\Services
 */
namespace App\Services;

use GrahamCampbell\Flysystem\FlysystemManager;
use Illuminate\Validation\ValidationException;

/**
 * Upload Service.
 * Handles image file uploads.
 * @author silver.ibenye
 *
 */
class UploadService
{
    /**
    * Accepted image extensions
    */
    const VALID_IMAGE_EXT = ['png', 'jpg', 'jpeg', 'gif'];

    /**
     *
     * @var FlysystemManager
     */
    private $flysystem;

    public function __construct(FlysystemManager $flysystem)
    {
        $this->flysystem = $flysystem;
    }

    /**
     * Gets the size of a file.
     *
     * @param string $fileString
     * @return mixed|int
     */
    private function getFileSize($fileString)
    {
        if (function_exists('mb_strlen')) {
            return mb_strlen($fileString, '8bit');
        } else {
            return strlen($fileString);
        }
    }

    /**
     * Validates the upload request.
     *
     * @param array $request
     * @throws ValidationException
     */
    private function validateUploadRequest($request)
    {
        if (empty($request['file'])) {
            throw new ValidationException(NULL, 'The File to be uploaded is required');
        }

        if (empty($request['contentType'])) {
            throw new ValidationException(NULL, 'The Upload-Content-Type header is required');
        }

        // ensure the contentType is in the right format
        if (!preg_match('/\w\/\w/', $request['contentType'])) {
            throw new ValidationException(NULL,
                'The Content-Type header should be in this format "{type}/{type extension}".');
        }

        // ensure the content is of type image.
        $contentType = preg_split('/\//', $request['contentType']) [0];
        if ($contentType != 'image') {
            throw new ValidationException(NULL, 'The content is not an image file.');
        }

        // ensure the image extension is supported
        $extension = preg_split('/\//', $request['contentType']) [1];
        if (!in_array($extension, self::VALID_IMAGE_EXT)) {
            throw new ValidationException(NULL, 'The image extension is not valid.');
        }

        // ensure the image size is within limit
        //you can use any size you prefer
        if ($this->getFileSize($request['file']) > 2048000) {
            throw new ValidationException(NULL, 'The image size is more than 2MB.'); 
        }
    }

    /**
     *
     * @param array $request
     * @throws ValidationException
     * @return array
     */
    public function uploadUserProfileImage($request)
    {
        $this->validateUploadRequest($request);

        $fileContent = $resquest['file'];

        $extension = preg_split('/\//', $request['contentType']) [1];

        // save image file.
        $filename = 'profile_image_' . time() . '.' . $extension;

        $filePath = $filename;

        // save file
        $this->flysystem->write($filePath, $fileContent);
        
        $response = array ();
        $response ['filePath'] = $filePath;

        return $response;
    }    
}

So here the request is first validated, the type is checked to make sure it is of type ‘image’, the type extension is checked to make sure it is a valid image extension, the size is checked to make sure it is within the accepted limit (you can use your own preferred limit). Then the image file is saved, this is done using a filesystem library called flysystem. Flysystem is great because it does all the work for you regardless of where you want to store the image, be it locally, or a remote location via ftp/sftp, or even to AWS S3 storage. All you have to do is provide the configuration for where you want it to store the image. Click on the link to learn how to integrate flysystem with the Lumen framework.

And there you have it. At least I hope this series helps you see and understand from a holistic view how the pieces fit together in regards to uploading images in a multitiered web application.