Skip to content

ImageField: bug with Symfony Image constraint #5227

@ajie62

Description

@ajie62

Describe the bug
Hi, I'm using EasyAdmin on a side project, and I decided to use an ImageField on a form to upload... well, an image. That being done, I tried to use it and it was working just fine, until I tried to put a constraint on the ImageField in the CrudController. Once you put on constraint on it, to prevent users to from uploading other types of files, you get the error 'The file could not be found.'. It is even impossible to create the entity.

To Reproduce

  • Create an entity with an Image property (string) and make a CRUD controller for it.
  • In the CrudController, try to use the Image constraint from Symfony.

(OPTIONAL) Additional context
This is what I've done:

ImageField::new('image')
                ->setBasePath('uploads/post-images')
                ->setUploadDir('public/uploads/post-images')
                ->setUploadedFileNamePattern('[slug]-[timestamp].[extension]')
                ->setFormTypeOptions([
                          'required' => false,
                          'constraints' => [
                                    new Image(),
                          ],
                ]),

I think using the ImageField should be enough. We shouldn't have to add an Image constraint, should we?

Activity

bocharsky-bw

bocharsky-bw commented on May 13, 2022

@bocharsky-bw
Contributor

Well explained! Based on this, I think this issue has 2 sub-issues:

  1. ImageField should allow uploading only images out of the box
  2. ImageField should allow adding Image constraint to add more strict limitations for specific cases
Digi92

Digi92 commented on Jun 27, 2022

@Digi92

Hi,
in case someone else tries to add a mime validation to the field type "ImageField".
The main problem is that the field type "ImageField" only passes the file name as a value to the validator instead of the object.
Therefore I created my own constraint which first loads the object out of the context and then passes it to the Symfony validator.
With this it works for me now.

Versions:
Symfony: 6.1
Eeasyadmin: 4.3.2

CrudController

ImageField::new('image')
             ->setBasePath('uploads/post-images')
             ->setUploadDir('public/uploads/post-images')
             ->setUploadedFileNamePattern('[slug]-[timestamp].[extension]')
            ->setFormTypeOption(
                'constraints',
                [
                    new \App\Validator\Constraints\EasyAdminFile([
                        'mimeTypes' => [ // We want to let upload only jpeg or png
                            'image/jpeg',
                            'image/png',
                        ],
                    ])
                ]
            );

src/Validator/Constraint/EasyAdminFile.php

<?php

namespace App\Validator\Constraints;

/**
 * @Annotation
 * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
 *
 * @property int $maxSize
 */
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class EasyAdminFile extends \Symfony\Component\Validator\Constraints\File
{
}

src/Validator/Constraint/EasyAdminFileValidator.php

<?php

namespace App\Validator\Constraints;

use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Model\FileUploadState;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

/**
 * This constraint is created for adding validation support to the easyAdmin field type "ImageField".
 * The Symfony constraint file validation is expecting an object of type "UploadedFile" or "FileObject" for
 * handling the validation but EasyAdmin only returned the filename.
 * Therefore, we have to load the object first before calling the symfony file validator.
 * Created for versions:
 * symfony: 6.1
 * easycorp/easyadmin-bundle: 4.3.2
 *
 * Class EasyAdminFileValidator
 * @package App\Validator\Constraints
 */
class EasyAdminFileValidator extends \Symfony\Component\Validator\Constraints\FileValidator
{
    /**
     * @param mixed $value
     * @param \Symfony\Component\Validator\Constraint $constraint
     * @return void
     */
    public function validate(mixed $value, Constraint $constraint)
    {
        if (!$constraint instanceof EasyAdminFile) {
            throw new UnexpectedTypeException($constraint, EasyAdminFile::class);
        }

        if ($value !== null &&
            $this->context->getObject() instanceof Form &&
            $this->context->getObject()->getConfig() instanceof FormBuilder
        ) {
            $config = $this->context->getObject()->getConfig();

            /** @var FileUploadState $state */
            $state = $config->getAttribute('state');

            if (!$state instanceof FileUploadState ||
                !$state->isModified()
            ) {
                return;
            }

            // On the upload field we can set the option for multiple uploads, so we need to take care of this
            foreach ($state->getUploadedFiles() as $index => $file) {
                parent::validate($file, $constraint);
            }
        }
    }
}

bocharsky-bw

bocharsky-bw commented on Jun 28, 2022

@bocharsky-bw
Contributor

I still think it should work out-of-the-box in EA, but your workaround is valid, thanks for sharing it!

Snowbaha

Snowbaha commented on Jun 30, 2022

@Snowbaha

@javiereguiluz It will be great to add the validation of Image with ImageField :)

bakhtiyor

bakhtiyor commented on Jun 30, 2022

@bakhtiyor

@Digi92 thanks for your implementation. I am getting following error message on using it, do you have any idea what is wrong?

image

abouross

abouross commented on Aug 12, 2022

@abouross

In fact currently it sends two values including the file name first then the file second, just ignore the validation if it is not an instance of FileUploaded:

src/Validator/EasyadminImage.php :

<?php


namespace App\Validator;


use Symfony\Component\Validator\Constraints\Image;

/**
 * @Annotation
 *
 * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
 */
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class EasyadminImage extends Image
{

}

src/Validator/EasyadminImageValidator.php :

<?php


namespace App\Validator;


use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\ImageValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

class EasyadminImageValidator extends ImageValidator
{
   public function validate($value, Constraint $constraint)
   {
       if (!$constraint instanceof EasyadminImage) {
           throw new UnexpectedTypeException($constraint, EasyadminImage::class);
       }
       if ($value instanceof UploadedFile)
           parent::validate($value, $constraint);
   }

}

usage example :

ImageField::new('image', 'Icon')
                    ->setColumns(4)
                    ->setUploadDir('public' . DIRECTORY_SEPARATOR . 'uploaded' . DIRECTORY_SEPARATOR . 'images')
                    ->setBasePath('uploaded' . DIRECTORY_SEPARATOR . 'images')
                    ->setUploadedFileNamePattern('[slug]-[contenthash].[extension]')
                    ->setHelp('Image .png, .jpg et jpeg uniquement au format 100 x 100 px')
                    ->setFormTypeOption('constraints', [new EasyadminImage(null, null, null, ['image/png', 'image/jpg', 'image/jpeg'],
                        100, 300, 100, 300, 1, 1)])
COil

COil commented on Jan 18, 2023

@COil
Contributor

I came to the same conclusion @abouross.

added this to the 4.x milestone on Feb 8, 2023
luismisanchez

luismisanchez commented on May 7, 2023

@luismisanchez

#5227 (comment)

Just to say thanks to @Digi92 . Working solution on Symfony 5.4.22 and Easyadmin 4.6.1.

psihius

psihius commented on Feb 7, 2024

@psihius
Contributor

@javiereguiluz do you have any ideas on how this one could be fixed so we do not need a workaround? I plan to try and solve this issue, so looking for some feedback if there's one before I dive into this.

Seb33300

Seb33300 commented on Apr 13, 2024

@Seb33300
Contributor

In fact currently it sends two values including the file name first then the file second, just ignore the validation if it is not an instance of FileUploaded:

This is because the same constraints are passed to the ImageField form type (which is a string saved in the entity) and to the underlying FileType.

In order to properly fix this issue, we should create a new custom option, for instance file_constraints that should be applied to the FileType only.

Seb33300

Seb33300 commented on Apr 13, 2024

@Seb33300
Contributor

I just created a PR to fix this issue: #6258

If you can, please try it!

added a commit that references this issue on May 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      Participants

      @javiereguiluz@COil@bakhtiyor@Seb33300@Digi92

      Issue actions

        ImageField: bug with Symfony Image constraint · Issue #5227 · EasyCorp/EasyAdminBundle