Mime Type Validation

Unless the authors that can upload these files are super, super trusted, like, you invited them to your wedding and they babysit your dog when you’re on vacation level of trusted… we need some validation. Right now, an author could upload literally any file type to the system.

No problem: find the controller. Hmm, there’s no form here. In ArticleAdminController, we put the validation on the form. Then we could check $form->isValid() and any errors rendered automatically.

But because we’re not inside a form, we need to validate directly… which is totally fine! Add another argument: ValidatorInterface $validator. This is the service that the form system uses internally for validation.


… lines 1 – 13

use

Symfony

\

Component

\

Validator

\

Validator

\

ValidatorInterface

;


… line 15

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 18 – 21

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 24 – 50

}

}

Then, before we do anything with that uploaded file, say $violations = $validator->validate(). Pass this the object that you want to validate. For us, it’s the $uploadedFile object itself. If we stopped here, it would read any validation annotations off of that class and apply those rules… which would be zero rules! This is a core class! There’s no validation rules, and we can’t just open up that file and add them. No worries: pass a second argument: the constraint to validate against.


… lines 1 – 15

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 18 – 21

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 24 – 26

$violations

=

$validator

->

validate

(

$uploadedFile

,


… lines 29 – 31

);


… lines 33 – 50

}

}

Remember: there are two main constraints for uploads: the Image constraint that we used before and the more generic File constraint, which we need here because the user can upload more than just images. Say new File() – the one from the Validator component.


… lines 1 – 12

use

Symfony

\

Component

\

Validator

\

Constraints

\

File

;


… lines 14 – 15

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 18 – 21

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 24 – 26

$violations

=

$validator

->

validate

(

$uploadedFile

,

new

File

([


… line 30

])

);


… lines 33 – 50

}

}

This constraint has two main options. The first is maxSize. Set it to 1k… just so we can see the error.


… lines 1 – 15

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 18 – 21

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 24 – 26

$violations

=

$validator

->

validate

(

$uploadedFile

,

new

File

([

‘maxSize’

=>

‘1k’

])

);


… lines 33 – 50

}

}

This $violations variable is basically an array of errors… except it’s not actually an array – it’s an object that holds errors. To check if anything failed validation, we can say if $violations->count() is greater than 0. For now, let’s just dd($violations) so we can see what it looks like.


… lines 1 – 15

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 18 – 21

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 24 – 26

$violations

=

$validator

->

validate

(


… lines 28 – 31

);

if

(

$violations

->

count

() >

0

) {

dd

(

$violations

);

}


… lines 37 – 50

}

}

Cool! Move over, select the Best Practices PDF – that’s definitely more than 1kb – and upload! Say hello to the ConstraintViolationList: a glorified array of ConstraintViolation error objects. And there’s the message: the file is too large. If you want, you can customize that message by passing the maxSizeMessage option… cause it is kind of a nerdy message.

So, in theory, you can have multiple validation rules and multiple errors. To keep things simple, let’s show the first error if there is one. Use $violation = $violations[0] to get it. The ConstraintViolationList class implements ArrayAccess, which is why we can use this syntax. Oh, and let’s help out my editor by telling it that this is a ConstraintViolation object.


… lines 1 – 13

use

Symfony

\

Component

\

Validator

\

ConstraintViolation

;


… lines 15 – 16

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 19 – 22

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 25 – 34

if

(

$violations

->

count

() >

0

) {

$violation

=

$violations

[

0

];


… lines 38 – 42

}


… lines 44 – 57

}

}

And now… hmm… how should we show this error to the user? This controller will eventually turn into an AJAX, or API endpoint that communicates via JSON. But because this is still a normal form submit, the easiest option is to put the error into a flash message and display it on the next page. Say $this->addFlash(), pass it an “error” type, and then $violation->getMessage().


… lines 1 – 16

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 19 – 22

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 25 – 34

if

(

$violations

->

count

() >

0

) {

$violation

=

$violations

[

0

];

$this

->

addFlash

(

‘error’

,

$violation

->

getMessage

());


… lines 39 – 42

}


… lines 44 – 57

}

}

Finish by stealing the redirect code from the bottom to send us back to the edit page.


… lines 1 – 16

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 19 – 22

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 25 – 34

if

(

$violations

->

count

() >

0

) {

$violation

=

$violations

[

0

];

$this

->

addFlash

(

‘error’

,

$violation

->

getMessage

());

return

$this

->

redirectToRoute

(

‘admin_article_edit’

, [

‘id’

=>

$article

->

getId

(),

]);

}


… lines 44 – 57

}

}

To render that flash message, open templates/base.html.twig and scroll down… I’m looking for the flash message logic we added in our Symfony series. There it is! We’re rendering success messages, but we don’t have anything to render error messages. Copy this, paste, and loop over error. Make it look scary with alert-danger.


… line 1

<

html

lang

=

“en”

>


… lines 3 – 15

<

body

>


… lines 17 – 73

{%

for

message

in

app.flashes(

‘error’

)

%}

<

div

class

=

“alert alert-danger”

>

{{ message }}

</

div

>

{%

endfor

%}


… lines 79 – 102

</

body

>

</

html

>

Cool! Test it out – refresh! And… nice! It redirects and there is our error.

This is great… but what we really want to do is control the types of files that are uploaded. Change the max size to 5m and add a mimeTypes option set to an array.

Tip

To allow files larger than 2MB, you’ll probably need to tweak the
upload_max_filesize setting in your php.ini file. Then, don’t forget
to restart your web server!


… lines 1 – 16

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 19 – 22

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 25 – 27

$violations

=

$validator

->

validate

(

$uploadedFile

,

new

File

([

‘maxSize’

=>

‘5M’

,

‘mimeTypes’

=> [


… lines 33 – 39

]

])

);


… lines 43 – 66

}

}

Let’s see… what do we want to allow? Well, probably any image is ok – so we can use image/* and definitely we should allow application/pdf.


… lines 1 – 16

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 19 – 22

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 25 – 27

$violations

=

$validator

->

validate

(

$uploadedFile

,

new

File

([

‘maxSize’

=>

‘5M’

,

‘mimeTypes’

=> [

‘image/*’

,

‘application/pdf’

,


… lines 35 – 39

]

])

);


… lines 43 – 66

}

}

But… what else? It’s tricky: there are a lot of mime types out there. A nice way to cheat is to press Shift+Shift and look for a core class called MimeTypeExtensionGuesser.

This is a pretty neat class: it’s what Symfony uses behind the scenes to “guess” the correct file extension based on the mime type of a file. It’s useful right now because it has a huge list of mime types and their extensions. Check it out: search for 'doc'. There it is: application/msword. And if you keep digging for other things like docx or xls, you can get a pretty good list of stuff you might want to accept.

Close this file and go back to the option: I’ll paste in a few mime types. This covers a lot your standard “document” stuff. Oh, I forgot one! Add application/vnd.ms-excel.


… lines 1 – 16

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 19 – 22

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 25 – 27

$violations

=

$validator

->

validate

(

$uploadedFile

,

new

File

([

‘maxSize’

=>

‘5M’

,

‘mimeTypes’

=> [

‘image/*’

,

‘application/pdf’

,

‘application/msword’

,

‘application/vnd.ms-excel’

,

‘application/vnd.openxmlformats-officedocument.wordprocessingml.document’

,

‘application/vnd.openxmlformats-officedocument.spreadsheetml.sheet’

,

‘application/vnd.openxmlformats-officedocument.presentationml.presentation’

,

‘text/plain’

]

])

);


… lines 44 – 67

}

}

Let’s try it out! Go back, select the Best Practices PDF, Upload and… no error! Try it again – but with this earth.zip file – that’s a zip of two photos. Submit and… error! But wow is that a wordy error. You an change that message with the mimeTypesMessage option.

Oh! There’s one last case we need to validate for. Hit enter on the URL to refresh the form. Do nothing and hit upload. Ah!!! Whoops! Everything explodes inside UploaderHelper… because there is no uploaded file! The horror!

Back in the controller, the second argument to validate() can accept an array of validation constraints. Put the new File into an array. Then add: new NotBlank() with a custom message: please select a file to upload.


… lines 1 – 13

use

Symfony

\

Component

\

Validator

\

Constraints

\

NotBlank

;


… lines 15 – 17

class

ArticleReferenceAdminController

extends

BaseController

{


… lines 20 – 23

public

function

uploadArticleReference

(

Article

$article

, Request

$request

, UploaderHelper

$uploaderHelper

, EntityManagerInterface

$entityManager

, ValidatorInterface

$validator

)

{


… lines 26 – 28

$violations

=

$validator

->

validate

(

$uploadedFile

,

[

new

NotBlank

(),

new

File

([


… lines 34 – 43

])

]

);


… lines 47 – 70

}

}

Refresh one more time. The huge error is replaced by a much more pleasant validation message.

Next: the author can upload a file reference… but it is literally impossible for them to download it. How can we make these private files accessible, but still check security first?

Xổ số miền Bắc