Symfony2 base64 in Entity to File in Form(和反向)使用DataTransformer

I got a blog plateform, with an entity Category this entity has associated to it

  • a name
  • an icon (small 40*40 png)

as the file is very small and mime-type assured to be always png, I've decided to store the file content as a Base64 encoded string in the entity/database

this way the page can be rendered at once without needing to do extra request for very small files (same for the API call GET /api/categories ). for pre-loaded data it works pretty well. The problem come when I want to edit one category

I've created a form with a file type for the icon

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $transformer = new FileToBase64Transformer();

    $iconField = $builder->create('iconColor', 'file', ['required' => false]);
    $iconField->addModelTransformer($transformer);

    $builder
        ->add('title')
        ->add($iconField)
    ;
}

the data transformer being

class FileToBase64Transformer implements DataTransformerInterface
{
    public function transform($base64)
    {
        return null;
    }
    public function reverseTransform($file)
    {
        if (is_null($file)) {
            return null;
        }

        return base64_encode(file_get_contents($file));
    }
}

but when i submit the form with a valid image, the form fails in the profiler, in form section i can see

Symfony\Component\Validator\ConstraintViolation Object(Symfony\Component\Form\Form).data.icon = THE_BASE_64_OF_THE_FILE

is it because the data is transformed before the validation happpens ?

  • how to modify this code to make it pass the validation
  • something i need to wrap my head around is what is going to happen when i will edit if the user upload no new image, how can the image be preserved ?

my current solution is to have two fields for the icon in my entity

/**
 * @Constraints\Image(
 *      maxWidth = 40,
 *      maxHeight = 40,
 *      allowSquare = true,
 *      mimeTypes = "image/png",
 * )
 */
private $iconFile = null;

/**
 * Base64 of the icon in color version
 *
 * @var string
 *
 * @ORM\Column(name="icon_color", type="text", nullable=true)
 */
private $icon = null;

(with the appropriate getter and setter)

then to have my data transformer for the WHOLE class Category

class FileToBase64Transformer implements DataTransformerInterface
{
    /**
     * Transforms an object (file) to a base64.
     *
     * @param  $file
     * @return string
     */
    public function transform($channel)
    {
        return $channel;
    }

    /**
     * reverse transforms
     *
     */
    public function reverseTransform($categoryl)
    {
        $iconFile = $category->getIconFile();
        if (!is_null($iconFile)) {
            $channel->setIcon(
                base64_encode(file_get_contents($iconFile))
            );
        }
        return $channel;
    }
}

and then in my CategoryType to associate the data transformer to the whole channel

/**
 * @param FormBuilderInterface $builder
 * @param array $options
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $transformer = new FileToBase64Transformer();

    $builder
        ->add('title')
        ->add('theme_color')
        ->add('type')
        ->add('iconFile')
        ->addModelTransformer($transformer)
    ;
}

the validation still happen after the data transform, but now it's no more a problem , as the entity will not be persisted if the form is not valid