title: Image upload with sonata admin
body: <p><strong>See EDIT above</strong></p>
  
  <p>I think the issue is pretty simple to solve but I can't find any clear answer right now. I hope you might have an idea.</p>
  
  <p>I'm trying to upload an image with sonata admin.</p>
  
  <p>In my entity I have this field</p>
  
  <pre><code>/**
   * @ORM\Column(type="string", length=2000)
   * @Assert\File(mimeTypes={ "image/png", "image/jpeg" })
   */
  private $image;
  </code></pre>
  
  <p>When I go to the sonata admin form view. The button Upload file is there and defined as below</p>
  
  <pre><code>$formMapper-&gt;add('image', FileType::class);
  </code></pre>
  
  <p>But when I try to send the form, I'm getting this error</p>
  
  <blockquote>
    <p>The form's view data is expected to be an instance of class Symfony\Component\HttpFoundation\File\File, but is a(n) string. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of Symfony\Component\HttpFoundation\File\File.</p>
  </blockquote>
  
  <p>I hint this is due to the doctrine string type. But I don't think doctrine has a "File" type.</p>
  
  <p>Thanks for your help.</p>
  
  <p><strong>EDIT:</strong></p>
  
  <p>Considering the link provided in comment, here is the new error</p>
  
  <blockquote>
    <p>The current field <code>image</code> is not linked to an admin. Please create one for the target entity : ``</p>
  </blockquote>
  
  <pre><code>&lt;?php
  
    namespace App\Entity;
    // src/Entity/Image.php
  
    class Image{
    const SERVER_PATH_TO_IMAGE_FOLDER = '/public/images';
  
    /**
     * Unmapped property to handle file uploads
     */
    private $file;
  
    /**
     * @param UploadedFile $file
     */
    public function setFile(UploadedFile $file = null)
    {
        $this-&gt;file = $file;
    }
  
    /**
     * @return UploadedFile
     */
    public function getFile()
    {
        return $this-&gt;file;
    }
  
    /**
     * Manages the copying of the file to the relevant place on the server
     */
    public function upload()
    {
        // the file property can be empty if the field is not required
        if (null === $this-&gt;getFile()) {
            return;
        }
  
       // we use the original file name here but you should
       // sanitize it at least to avoid any security issues
  
       // move takes the target directory and target filename as params
       $this-&gt;getFile()-&gt;move(
           self::SERVER_PATH_TO_IMAGE_FOLDER,
           $this-&gt;getFile()-&gt;getClientOriginalName()
       );
  
       // set the path property to the filename where you've saved the file
       $this-&gt;filename = $this-&gt;getFile()-&gt;getClientOriginalName();
  
       // clean up the file property as you won't need it anymore
       $this-&gt;setFile(null);
   }
  
   /**
    * Lifecycle callback to upload the file to the server.
    */
   public function lifecycleFileUpload()
   {
       $this-&gt;upload();
   }
  
   /**
    * Updates the hash value to force the preUpdate and postUpdate events to fire.
    */
   public function refreshUpdated()
   {
      $this-&gt;setUpdated(new \DateTime());
   }
  
   // ... the rest of your class lives under here, including the generated fields
   //     such as filename and updated
   }
  </code></pre>
  
  <p>In my ForumAdmin, now I have</p>
  
  <pre><code>protected function configureFormFields(FormMapper $formMapper)
  {
      $formMapper-&gt;add('name', TextType::class);
      $formMapper-&gt;add('description', TextAreaType::class);
      $formMapper-&gt;add('weight', IntegerType::class);
      $formMapper-&gt;add('category', EntityType::class, [
          'class' =&gt; Category::class,
          'choice_label' =&gt; 'name',
      ]);
      $formMapper
          -&gt;add('image', AdminType::class)
      ;
  }
  
  protected function configureDatagridFilters(DatagridMapper $datagridMapper)
  {
      $datagridMapper-&gt;add('name');
      $datagridMapper-&gt;add('category');
  }
  
  protected function configureListFields(ListMapper $listMapper)
  {
      $listMapper-&gt;addIdentifier('name');
      $listMapper-&gt;addIdentifier('description');
      $listMapper-&gt;addIdentifier('category');
      $listMapper-&gt;addIdentifier('weight');
      $listMapper-&gt;addIdentifier('createdAt');
      $listMapper-&gt;addIdentifier('updatedAt');
      $listMapper-&gt;addIdentifier('image');
  }
  
  public function prePersist($object)
  {
      parent::prePersist($object);
      $this-&gt;manageEmbeddedImageAdmins($object);
      if($object instanceof Forum){
          $object-&gt;setCreatedAt(new \DateTime('now'));
          $object-&gt;setUpdatedAt(new \DateTime('now'));
          $object-&gt;setStatus("NO_NEW");
      }
  
  }
  
  public function preUpdate($page)
  {
      $this-&gt;manageEmbeddedImageAdmins($page);
  }
  
  private function manageEmbeddedImageAdmins($page)
  {
      // Cycle through each field
      foreach ($this-&gt;getFormFieldDescriptions() as $fieldName =&gt; $fieldDescription) {
          // detect embedded Admins that manage Images
          if ($fieldDescription-&gt;getType() === 'sonata_type_admin' &amp;&amp;
              ($associationMapping = $fieldDescription-&gt;getAssociationMapping()) &amp;&amp;
              $associationMapping['targetEntity'] === 'App\Entity\Image'
          ) {
              $getter = 'get'.$fieldName;
              $setter = 'set'.$fieldName;
  
              /** @var Image $image */
              $image = $page-&gt;$getter();
  
              if ($image) {
                  if ($image-&gt;getFile()) {
                      // update the Image to trigger file management
                      $image-&gt;refreshUpdated();
                  } elseif (!$image-&gt;getFile() &amp;&amp; !$image-&gt;getFilename()) {
                      // prevent Sf/Sonata trying to create and persist an empty Image
                      $page-&gt;$setter(null);
                  }
              }
          }
      }
  }
  </code></pre>
  
  <p>I also have this ImageAdmin even if I don't see why it would be usefull</p>
  
  <pre><code>final class ImageAdmin extends AbstractAdmin{
  protected function configureFormFields(FormMapper $formMapper)
  {
      $formMapper
          -&gt;add('image', FileType::class);
  }
  
  public function prePersist($image)
  {
      $this-&gt;manageFileUpload($image);
  }
  
  public function preUpdate($image)
  {
      $this-&gt;manageFileUpload($image);
  }
  
  private function manageFileUpload($image)
  {
      if ($image-&gt;getFile()) {
          $image-&gt;refreshUpdated();
      }
  }
  
  // ...
  }
  </code></pre>