Hello everyone!

We have been investing plenty of personal time and energy for many years to share our knowledge with you all. However, we now need your help to keep this blog running. All you have to do is just click one of the adverts on the site, otherwise it will sadly be taken down due to hosting etc. costs. Thank you.

As we know www is the public web root of symfony framework so if you keep your images in there, they are accessible by anyone which is not a problem in certain cases. However, you'll sometimes need to keep some kind of images in private and prevent people seing them unless they have access rights. In this example we are going to upload images to a private directory app/Resources/private/uploads/images and serve them in twig templates with a controller for security reasons.


Files will be uploaded to app/Resources/private/uploads/images. When it comes to serving files, all we have to do is to call controller endpoint in twig file with <img src="{{ path('image_show', {'name': image_name}) }}" />. This will then look like <img src="/app_dev.php/6f3f48c823a61a2919ad6092ffe0f1e0.png"> in HTML source so the actual image path is not exposed at all.


$ HTTPDUSER=`ps axo user,comm | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\  -f1`
$ sudo setfacl -R -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX app/Resources/private/uploads/images
$ sudo setfacl -dR -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX app/Resources/private/uploads/images


image_upload_path: '%kernel.root_dir%/Resources/private/uploads/images'


resource: "@InanzzzApplicationBundle/Controller/"
type: annotation
prefix: /


Just to keep the example as short as possible, I'm keeping whole logic in controller but remember it is not a good practise.

namespace Inanzzz\ApplicationBundle\Controller;

use Inanzzz\ApplicationBundle\Form\Model\ImageUpload as ImageUploadModel;
use Inanzzz\ApplicationBundle\Form\Type\ImageUploadType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\RouterInterface;

* @Route("", service="inanzzz_application.controller.image")
class ImageController
private $templating;
private $formFactory;
private $router;
private $imageUploadPath;

public function __construct(
EngineInterface $templating,
FormFactoryInterface $formFactory,
RouterInterface $router,
) {
$this->templating = $templating;
$this->formFactory = $formFactory;
$this->router = $router;
$this->imageUploadPath = $imageUploadPath;

* @Method({"GET"})
* @Route("/", name="image_index")
* @return Response
public function indexAction()
return $this->templating->renderResponse(
'form' => $this->createForm()->createView(),
'images' => $this->getImages(),

* @param string $name
* @Method({"GET"})
* @Route("/{name}", name="image_show")
* @return Response
public function showAction($name)
$response = new BinaryFileResponse($this->imageUploadPath.'/'.$name);
iconv('UTF-8', 'ASCII//TRANSLIT', $name)

return $response;

* @param Request $request
* @Method({"POST"})
* @Route("/upload/image", name="image_upload_image")
* @return Response
public function uploadImageAction(Request $request)
$imageUploadModel = new ImageUploadModel();

$form = $this->createForm($imageUploadModel);

if ($form->isSubmitted() && $form->isValid()) {

/** @var UploadedFile $image */
$image = $imageUploadModel->image;
$name = md5(uniqid().microtime().$image->getClientOriginalName()).'.'.$image->guessExtension();

$image->move($this->imageUploadPath, $name);

return new RedirectResponse($this->router->generate('image_index'));

private function createForm(ImageUploadModel $imageUploadModel = null)
return $this->formFactory->create(
$imageUploadModel ? $imageUploadModel : new ImageUploadModel(),
'method' => 'POST',
'action' => $this->router->generate('image_upload_image')

private function getImages()
$images = [];

$finder = new Finder();

/** @var SplFileInfo $file */
foreach ($finder as $file) {
$images[] = $file->getFilename();

return $images;

This is your controller service definition.

class: Inanzzz\ApplicationBundle\Controller\ImageController
- '@templating'
- '@form.factory'
- '@router'
- '%image_upload_path%'


{% block body %}

<hr />
{{ form_start(form) }}
{{ form_row(form.image) }}
<button type="submit">Upload</button>
{{ form_end(form) }}

<hr />
{% for image in images %}
<img src="{{ path('image_show', {'name': image}) }}" />
{% else %}
<em>No image found.</em>
{% endfor %}
{% endblock %}


namespace Inanzzz\ApplicationBundle\Form\Type;

use Inanzzz\ApplicationBundle\Form\Model\ImageUpload as ImageUploadModel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;

class ImageUploadType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
$builder->add('image', FileType::class, ['label' => 'Select an image']);

public function configureOptions(OptionsResolver $resolver)
$resolver->setDefaults(['data_class' => ImageUploadModel::class]);


I am not validating the user input but you definitelly should. Again, keeping things short for now.

namespace Inanzzz\ApplicationBundle\Form\Model;

class ImageUpload
public $image;