Example below lets us upload JPG, GIF and PNG images, create thumbnails, centers and adds padding to the images.


Upload folder structure


You can use "web" folder instead of "app" folder if you wish. Make sure you have an empty .gitkeep file.


app
upload
image
thumbnail
.gitkeep

Update .gitignore in project root


With these lines, git will ignore all uploaded files so that they don't get committed and pushed to remote repository.


/app/upload/image/*.*
/app/upload/image/thumbnail/*.*
!app/upload/image/thumbnail/.gitkeep

Assign permissions


# MAC OS
Inanzzz-MacBook-Pro:sport inanzzz$ HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1`
Inanzzz-MacBook-Pro:sport inanzzz$ sudo chmod -R +a "$HTTPDUSER allow delete,write,append,file_inherit,directory_inherit" app/upload
Inanzzz-MacBook-Pro:sport inanzzz$ sudo chmod -R +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/upload

# UBUNTU version.1
Inanzzz-MacBook-Pro:sport inanzzz$ HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1`
Inanzzz-MacBook-Pro:sport inanzzz$ sudo setfacl -R -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX app/upload
Inanzzz-MacBook-Pro:sport inanzzz$ sudo setfacl -dR -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX app/upload

# UBUNTU version.2
Inanzzz-MacBook-Pro:sport inanzzz$ chmod -R 755 app/upload
Inanzzz-MacBook-Pro:sport inanzzz$ chown -R www-data:www-data app/upload

Install GD library


Assuming that it is already installed. If not then just Google it and install it. Ubuntu users may not experience issues but Mac OS users might have problems with PNG support that is not been enabled in php. If so then do the following.


PNG is not enabled - phpinfo()



Inanzzz-MacBook-Pro:~ inanzzz$ curl -s http://php-osx.liip.ch/install.sh | bash -s 5.6
......
......
......
Restarting Apache
Syntax OK
Inanzzz-MacBook-Pro:~ inanzzz$ export PATH=/usr/local/php5/bin:$PATH
Inanzzz-MacBook-Pro:~ inanzzz$ sudo apachectl graceful

PNG is now enabled - phpinfo()



Parameters YML


parameters:
image_upload:
image_upload_dir: /upload/image/
thumbnail_upload_dir: /upload/image/thumbnail/
valid_extensions:
- jpg
- jpeg
- gif
- png
thumbnail_defaults:
height: 200
width: 200
red: 200
green: 0
blue: 0

Services YML


services:
football_frontend.service.image_upload:
class: Football\FrontendBundle\Service\ImageUploadService
arguments: [%kernel.root_dir%, %image_upload%]

ImageUploadService class


This just gives you a basic idea so you can/should improve it as you wish.


namespace Football\FrontendBundle\Service;

/**
* Uploads image and creates thumbnail. Image does not get distorted when resized.
*
* Use:
* $thumbnail = array('thumbnail' => true, 'padding' => true);
* $this->get('image_uploader')->upload($submission->getFlag(), $thumbnail);
*/
class ImageUploadService
{
private $imageUploadDir;
private $thumbnailUploadDir;
private $thumbnailDefaults;
private $validExtensions;

/**
* @param $root
* @param $imageUpload
*/
public function __construct($root, $imageUpload)
{
$this->imageUploadDir = $root . $imageUpload['image_upload_dir'];
$this->thumbnailUploadDir = $root . $imageUpload['thumbnail_upload_dir'];
$this->thumbnailDefaults = $imageUpload['thumbnail_defaults'];
$this->validExtensions = $imageUpload['valid_extensions'];
}

/**
* Image gets uploaded.
*
* @param $image
* @param array $thumbnail
* @return array|string
*/
public function upload($image, array $thumbnail = null)
{
$extension = $image->guessExtension();

if (! in_array($extension, $this->validExtensions)) {
return 'Invalid image format.';
}

$originalName = strip_tags($image->getClientOriginalName());
$newName = sha1(crc32(time()) . microtime() . $originalName) . '.' . $extension;

$image->move($this->imageUploadDir, $newName);

if (! file_exists($this->imageUploadDir . $newName)) {
return 'Image could not be uploaded.';
}

if (! is_null($thumbnail) && count($thumbnail) == 2) {
if ($thumbnail['thumbnail'] === true) {
if ($thumbnail['padding'] === true) {
$this->createThumbnailWithPadding(
$this->imageUploadDir . $newName,
$this->thumbnailUploadDir . $newName
);
} else {
$this->createThumbnailWithoutPadding(
$this->imageUploadDir . $newName,
$this->thumbnailUploadDir . $newName
);
}
}
}

return array('originalName' => $originalName, 'newName' => $newName);
}

/**
* Resizes image with adding padding to the shortest edge and centers the image.
* Transparency of image is destroyed.
*
* @param $sourceImage
* @param $targetImage
* @return bool
*/
private function createThumbnailWithPadding($sourceImage, $targetImage)
{
list($sourceWidth, $sourceHeight, $sourceType) = getimagesize($sourceImage);

$sourceGdImage = imagecreatefromstring(file_get_contents($sourceImage));

//Determine scale based on the longest edge
if ($sourceHeight > $sourceWidth) {
$scale = ($this->thumbnailDefaults['height'] / $sourceHeight);
} else {
$scale = ($this->thumbnailDefaults['width'] / $sourceWidth);
}

//Calculate new image dimensions
$thumbnailWidth = ($sourceWidth * $scale);
$thumbnailHeight = ($sourceHeight * $scale);

//Determine offset coordinates so that new image is centered
$offsetX = (($this->thumbnailDefaults['width'] - $thumbnailWidth) / 2);
$offsetY = (($this->thumbnailDefaults['height'] - $thumbnailHeight) / 2);

//Create new image and fill with background colour
$thumbnailGdImage = imagecreatetruecolor($this->thumbnailDefaults['width'], $this->thumbnailDefaults['height']);

//Set background colour
$bgColor = imagecolorallocate(
$thumbnailGdImage,
$this->thumbnailDefaults['red'],
$this->thumbnailDefaults['green'],
$this->thumbnailDefaults['blue']
);

//Fill background colour
imagefill($thumbnailGdImage, 0, 0, $bgColor);
//Copy and resize original image into center of new image
imagecopyresampled(
$thumbnailGdImage,
$sourceGdImage,
$offsetX,
$offsetY,
0,
0,
$thumbnailWidth,
$thumbnailHeight,
$sourceWidth,
$sourceHeight
);

//clearstatcache();

switch ($sourceType) {
case IMAGETYPE_GIF:
imagegif($thumbnailGdImage, $targetImage, 90);
break;
case IMAGETYPE_JPEG:
imagejpeg($thumbnailGdImage, $targetImage, 90);
break;
case IMAGETYPE_PNG:
imagepng($thumbnailGdImage, $targetImage, 9);
break;
}

imagedestroy($sourceGdImage);
imagedestroy($thumbnailGdImage);

return true;
}

/**
* Resizes image without adding padding to short edge.
* Transparency of image is preserved.
*
* @param $sourceImage
* @param $targetImage
* @return bool
*/
private function createThumbnailWithoutPadding($sourceImage, $targetImage)
{
list($sourceWidth, $sourceHeight, $sourceType) = getimagesize($sourceImage);

switch ($sourceType) {
case IMAGETYPE_GIF:
$sourceGdImage = imagecreatefromgif($sourceImage);
break;
case IMAGETYPE_JPEG:
$sourceGdImage = imagecreatefromjpeg($sourceImage);
break;
case IMAGETYPE_PNG:
$sourceGdImage = imagecreatefrompng($sourceImage);
break;
}

if ($sourceGdImage === false) {
return false;
}

$sourceAspectRatio = ($sourceWidth / $sourceHeight);
$thumbnailAspectRatio = ($this->thumbnailDefaults['width'] / $this->thumbnailDefaults['height']);

if ($sourceWidth <= $this->thumbnailDefaults['width'] && $sourceHeight <= $this->thumbnailDefaults['height']) {
$thumbnailWidth = $sourceWidth;
$thumbnailHeight = $sourceHeight;
} elseif ($thumbnailAspectRatio > $sourceAspectRatio) {
$thumbnailWidth = (int) ($this->thumbnailDefaults['height'] * $sourceAspectRatio);
$thumbnailHeight = $this->thumbnailDefaults['height'];
} else {
$thumbnailWidth = $this->thumbnailDefaults['width'];
$thumbnailHeight = (int) ($this->thumbnailDefaults['width'] / $sourceAspectRatio);
}

$thumbnailGdImage = imagecreatetruecolor($thumbnailWidth, $thumbnailHeight);

//Keep the transparency
imagecolortransparent($thumbnailGdImage, imagecolorallocatealpha($thumbnailGdImage, 0, 0, 0, 127));
imagealphablending($thumbnailGdImage, false);
imagesavealpha($thumbnailGdImage, true);

imagecopyresampled(
$thumbnailGdImage,
$sourceGdImage,
0,
0,
0,
0,
$thumbnailWidth,
$thumbnailHeight,
$sourceWidth,
$sourceHeight
);

//clearstatcache();

switch ($sourceType) {
case IMAGETYPE_GIF:
imagegif($thumbnailGdImage, $targetImage, 90);
break;
case IMAGETYPE_JPEG:
imagejpeg($thumbnailGdImage, $targetImage, 90);
break;
case IMAGETYPE_PNG:
imagepng($thumbnailGdImage, $targetImage, 9);
break;
}

imagedestroy($sourceGdImage);
imagedestroy($thumbnailGdImage);

return true;
}
}

Controller


class CountryController
{
const ROUTER_PREFIX = 'football_frontend_country_';

public function createProcessAction(Request $request)
{
if ($request->getMethod() != 'POST') {
throw new Exception('Country create: only POST method is allowed.');
}

$form = $this->getForm(
new Country(),
'POST',
$this->generateUrl(self::ROUTER_PREFIX . 'create')
);
$form->handleRequest($request);

if (!$form->isSubmitted()) {
throw new Exception('Country create: form is not submitted.');
}

if ($form->isValid() !== true) {
return $this->getFormView(
'create',
[
'form' => $form->createView()
]
);
}

try {
$data = $form->getData();

$thumbnail = array('thumbnail' => true, 'padding' => false);
$upload = $this->get('football_frontend.service.image_upload')->upload($data->getFlag(), $thumbnail);

if (! is_array($upload)) {
exit('Do something with failure.');
}

echo '<pre>';
print_r($upload);
exit;

// Do something with result
} catch (Exception $e) {
throw new Exception(sprintf('Exception [%s]: %s', $e->getCode(), $e->getMessage()));
}
}
}

FormType


namespace Football\FrontendBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\Validator\Constraints\NotBlank;

class CountryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options = [])
{
$builder
->setMethod($options['method'])
->setAction($options['action'])
->add(
'flag',
'file',
[
'error_bubbling' => true,
'constraints' => [
new NotBlank(
[
'message' => 'Flag is required.'
]
),
new File(
[
'maxSize' => '1000000', // 1MB
'mimeTypes' => ['image/png','image/jpeg','image/jpg','image/gif'],
'uploadErrorMessage' => 'Flag file could not be uploaded for some unknown reason.'
]
)
]
]
);
}

public function getName()
{
return 'country';
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
['data_class' => 'Football\FrontendBundle\Entity\Country']
);
}
}

Output


Array
(
[originalName] => house.png
[newName] => 66db00a41de42a1658b0d9d9bf5e400c2e57b144.png
)