Symfony2: manual file upload with VichUploaderBundle
Accepted answer is not correct (anymore?). According with the usage documentation
you can indeed manually upload a File
without using Symfony's Form Component:
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*/
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTime('now');
}
}
You should use Symfony\Component\HttpFoundation\File\UploadedFile
instead of File
:
$file = new UploadedFile($filename, $filename, null, filesize($filename), false, true);
VichUploaderBundle
will handle this object.
To combine the answers and address an issue with VichUploader and inject_on_load: true
changing the updatedAt
during the postLoad
event.
Explanation of the issue
Since the VichUploader mapped property is not monitored by Doctrine ORM, you will need to ensure at least one of the properties that the ORM manages is changed to trigger the prePersist
or preUpdate
events that VichUploader uses to manage the file reference.
This is generally done by using an updatedAt
DATETIME column in your file setter but can be any property that is managed by the ORM.
postLoad
event setter File
instance issue
Verifying an instance of UploadedFile
is supplied to the mapped property setter method, will ensure that updatedAt
is changed only during Form submissions or when manually supplied, instead of during the postLoad
event - where VichUpload supplies an instance of File
to the same mapped setter method. Which would otherwise cause the updatedAt
value in your views to change and potentially change the database value if you call $em::flush()
when instances of the Image
object are loaded by Doctrine and therefor managed.
App\Entity\Image
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* @ORM\Entity
* @Vich\Uploadable
*/
class Image
{
/**
* @var \DateTime
* @ORM\Column(name="updated_at", type="datetime")
*/
private $updatedAt;
/**
* @var \Symfony\Component\HttpFoundation\File\File
* @Vich\UploadableField(mapping="your_mapping", fileNameProperty="image")
*/
private $imageFile;
/**
* @var string|null
* @ORM\Column(name="image", type="string", length=255, nullable=true)
*/
private $image;
public function __construct()
{
$this->updatedAt = new \DateTime('now');
}
//...
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image instanceof UploadedFile) {
$this->updatedAt = new \DateTime('now');
}
}
}
Then you can manually define the file to use in your entity by instantiating an UploadedFile
object and setting the error
argument as null
and the test
argument to true
, which disables validating UPLOAD_ERR_OK
and is_uploaded_file()
[sic].
Symfony <= 4.0
use Symfony\Component\HttpFoundation\File\UploadedFile;
$file = new UploadedFile($path, $filename, null, filesize($path), null, true);
Symfony 4.1+
In Symfony 4.1 and later, the file size argument was deprecated.
use Symfony\Component\HttpFoundation\File\UploadedFile;
$file = new UploadedFile($path, $filename, null, null, true);
Now you can successfully persist and/or flush your entity with the manually uploaded file.
$image = new Image();
$image->setImageFile($file);
$em->persist($image);
$em->flush();