EasyAdminBundle icon indicating copy to clipboard operation
EasyAdminBundle copied to clipboard

ImageField: bug with Symfony Image constraint

Open ajie62 opened this issue 3 years ago • 11 comments

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?

ajie62 avatar May 13 '22 14:05 ajie62

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

bocharsky-bw avatar May 13 '22 14:05 bocharsky-bw

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);
            }
        }
    }
}

Digi92 avatar Jun 27 '22 20:06 Digi92

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

bocharsky-bw avatar Jun 28 '22 09:06 bocharsky-bw

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

Snowbaha avatar Jun 30 '22 07:06 Snowbaha

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

image

bakhtiyor avatar Jun 30 '22 15:06 bakhtiyor

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)])

abouross avatar Aug 12 '22 18:08 abouross

I came to the same conclusion @abouross.

COil avatar Jan 18 '23 11:01 COil

https://github.com/EasyCorp/EasyAdminBundle/issues/5227#issuecomment-1167821550

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

luismisanchez avatar May 07 '23 11:05 luismisanchez

@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.

psihius avatar Feb 07 '24 07:02 psihius