Buy

All about Uploading Files in Symfony

0%
Buy

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

I've ignored it long enough - sorry! We've gotta add some validation to the upload field. Because... right now, we can upload any file type - it's madness! This is supposed to be an image field people! We need to only allow pngs, jpegs, gifs, image stuff.

Validating an Unmapped Field

Normally we add validation to the entity class: we would go into the Article class, find the property, and add some annotations. But... the field we want to validate is an unmapped form field - there is no imageFile property in Article.

No worries: for unmapped fields, you can add validation directly to the form with the constraints option. And when it comes to file uploads, there are two really important constraints: one called File and an even stronger one called Image. Add new Image() - the one from the Validator\Constraints.

... lines 1 - 18
use Symfony\Component\Validator\Constraints\Image;
class ArticleFormType extends AbstractType
{
... lines 23 - 29
public function buildForm(FormBuilderInterface $builder, array $options)
{
... lines 32 - 35
$builder
... lines 37 - 54
->add('imageFile', FileType::class, [
... lines 56 - 57
'constraints' => [
new Image()
]
])
;
... lines 63 - 95
}
... lines 97 - 159
}

The Image Constraint

And... that's all we need! That's enough to make sure the user uploads an image. Check it out: find your browser, Google for "Symfony image constraint" and click into the docs.

The Image constraint extends the File constraint - so both basically have the same behavior: you can define a maxSize or configure different mimeTypes. The Image constraint just adds... more super-powers. First, it pre-configures the mimeType option to only allow images. And you get a crazy-amount of other image stuff - like minWidth, maxWidth or allowPortrait.

So let's test it! Refresh the page and browse. Oh, the Symfony Best Practices PDF snuck into my directory. Select that, update and... boom! This file is not a valid image.

Validating the File Size

Go back to the docs and click to see the File constraint. The other most common option is maxSize. To see what that looks like, set it to something tiny, like 5k.

... lines 1 - 20
class ArticleFormType extends AbstractType
{
... lines 23 - 29
public function buildForm(FormBuilderInterface $builder, array $options)
{
... lines 32 - 35
$builder
... lines 37 - 54
->add('imageFile', FileType::class, [
... lines 56 - 57
'constraints' => [
new Image([
'maxSize' => '5k'
])
]
])
;
... lines 65 - 97
}
... lines 99 - 161
}

Ok: browse and select any of the files. Hit update and... perfect: the file is too large.

Change that back to 5M, or whatever makes sense for you.

... lines 1 - 20
class ArticleFormType extends AbstractType
{
... lines 23 - 29
public function buildForm(FormBuilderInterface $builder, array $options)
{
... lines 32 - 35
$builder
... lines 37 - 54
->add('imageFile', FileType::class, [
... lines 56 - 57
'constraints' => [
new Image([
'maxSize' => '5M'
])
]
])
;
... lines 65 - 97
}
... lines 99 - 161
}

Validation and upload_max_filesize

Oh, but, remember a few minutes ago when we tried to upload the stars photo? It's 3 megabytes, which is way under the 5 megabytes we just set, but above my php.ini upload_max_filesize setting. That caused a really nasty error.

Well, try selecting it again and updating. Yes! When you use the File or Image constraint, they also catch any PHP-level upload errors and display them quite nicely. You can customize this message.

Making the Upload Field Required

And... that's it! Sure, there are a more options and you can control all the messages - but that's easy enough. Except... there is one tricky thing: how can we make the upload field required? Like, when someone creates an article, they should be required to upload an image before saving it.

Simple, right? Just add a new NotNull() constraint to the imageFile field. Wait, no, that won't work. If we did that, we would need to upload a file even if we were just editing a field on the article: we would literally need to upload an image every time we changed anything.

Okay: so we want the imageFile to be required... but only if the Article doesn't already have an imageFilename. Start by breaking this onto multiple lines. Then say $imageConstraints =, copy the new Image() stuff and paste it here.

... lines 1 - 20
class ArticleFormType extends AbstractType
{
... lines 23 - 29
public function buildForm(FormBuilderInterface $builder, array $options)
{
... lines 32 - 35
$builder
... lines 37 - 45
->add('location', ChoiceType::class, [
... lines 47 - 53
])
;
... line 56
$imageConstraints = [
new Image([
'maxSize' => '5M'
])
];
$builder
->add('imageFile', FileType::class, [
... lines 64 - 66
])
;
... lines 69 - 101
}
... lines 103 - 165
}

Down below, set 'constraints' => $imageConstraints. Oh... and let's spell that correctly.

... lines 1 - 20
class ArticleFormType extends AbstractType
{
... lines 23 - 29
public function buildForm(FormBuilderInterface $builder, array $options)
{
... lines 32 - 61
$builder
->add('imageFile', FileType::class, [
... lines 64 - 65
'constraints' => $imageConstraints
])
;
... lines 69 - 101
}
... lines 103 - 165
}

Now we can conditionally add the NotNull() constraint exactly when we need it. Scroll up a little. In our forms tutorial, we used the data option to get the Article object that this form is bound to. If this is a "new" form, there may or may not be an Article object - so this will be an Article object or null. I also used that to create an $isEdit variable to figure out if we're on the edit screen or not.

We can leverage that by saying if this is not the edit page or if the article doesn't have an image filename, then take $imageConstraints and add new NotNull(). We'll even get fancy and customize the message: Please upload an image.

... lines 1 - 19
use Symfony\Component\Validator\Constraints\NotNull;
class ArticleFormType extends AbstractType
{
... lines 24 - 57
$imageConstraints = [
new Image([
'maxSize' => '5M'
])
];
if (!$isEdit || !$article->getImageFilename()) {
$imageConstraints[] = new NotNull([
'message' => 'Please upload an image',
]);
}
... lines 69 - 109
}
... lines 111 - 173
}

Just saying if !$isEdit is probably enough... but just in case, I'm checking to see if, somehow, we're on the edit page, but the imageFilename is missing, let's require it.

Cool: testing time! Refresh the entire form, but don't select an upload: we know that this Article does have an image already attached. Hit update and... works fine! Now try creating a new Article, fill in a few of the required fields, hit create and... boom! Please upload an image!

Validation, check! Next, let's fix how this renders: we've gotta see the filename after selecting a file - seeing nothing is bummin' me out.

Leave a comment!

  • 2019-05-20 Giacomo Balloccu

    I'm doing like that because, I got a single Entity but I have slightly different type of article, one has the image and one instead of that holds a pdf. I tried to pass the article with his type already set from the controller to the form but I wasn't able to do that because when I do $article->setType(0) the controller say UNDEFINED VARIABLE.
    I need the type in the form because I wanna render some fields only, and because they hold the constraints I can't just render all the fields, because if I try to submit a type 0 article I get that the pdf is missing and viceversa.

    For the moment I have done 2 different type of forms is that a bad idea?

  • 2019-05-20 Diego Aguiar

    Hey Giacomo Balloccu

    Why you have to pass a hidden value? If you have a specific section for creating your articles, then, on your controller you can set that entity up instead of relying on hidden data on your form. If you are not working with a Entity Form Type, then you can access to the data as a normal array, i.e. $article['id'] but why it's not an entity anymore?

    Cheers!

  • 2019-05-20 Giacomo Balloccu

    Hi! I'm passing already something to my form, I'm using a single entity for news which is very similar to another type of article so I have used in the db a flag, so I need to pass a HiddenValue to enable or not some form validation (correct me if there is a better way to do that), the problem is that part of the code:

    /** @var Article|null $article */
    $article = $options['data_class'];
    $type = $options['type'];
    $isEdit = $article && $article->getId();

    I'm not able to extract $article because now is an array and no more a single object so I get:

    Call to a member function getId() on array

    How can I solve that?