I have followed Multiple file upload with Symfony2 and created a entity
/*
* #ORM\HasLifecycleCallbacks
* #ORM\Entity(repositoryClass="Repair\StoreBundle\Entity\attachmentsRepository")
*/
class attachments
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #Assert\File(maxSize="6000000")
* #ORM\Column(name="files", type="array", length=255, nullable=true)
*/
private $files=array();
/**
* Get id
*
* #return integer
*/
public function getId() {
return $this->id;
}
/**
* Set files
* #param object $files
*
* #return attachments
*/
public function setFiles($files) {
$this->files = $files;
}
/**
* Get files
*
* #return object
*/
public function getFiles() {
return $this->files;
}
public function __construct() {
$files = array();
}
public function uploadFiles() {
// the files property can be empty if the field is not required
if (null === $this->files) {
return;
}
if (!$this->id) {
$this->files->move($this->getTmpUploadRootDir(), $this->files->getClientOriginalName());
} else {
$this->files->move($this->getUploadRootDir(), $this->files->getClientOriginalName());
}
$this->setFiles($this->files->getClientOriginalName());
}
public function getAbsolutePath() {
return null === $this->path
? null
: $this->getUploadRootDir() . DIRECTORY_SEPARATOR . $this->path;
}
public function getWebPath() {
return null === $this->path
? null
: $this->getUploadDir() . DIRECTORY_SEPARATOR . $this->path;
}
protected function getUploadRootDir() {
return __DIR__ . '/../../../../web/'. $this->getUploadDir();
}
protected function getUploadDir() {
return 'uploads/';
}
}
I have a controller which has the folowing code
class uploadController extends Controller
{
public function uploadAction(Request $request) {
$id= $_GET['id'];
$user = new attachments();
$form = $this->createFormBuilder($user)->add('files','file',array("attr"=>array("multiple" =>"multiple",)))->getForm();
$formView = $form->createView();
$formView->getChild('files')->set('full_name','form[file][]');
if ($request->getMethod() == 'POST') {
$em = $this->getDoctrine()->getManager();
$form->bind($request);
$files = $form["files"]->getFilenames();
$user->uploadFiles(); //--here iam not able to get te file names in order to send to the upload function.upload files is returning null values
}
}
}
the controller is not able to get the filenames that is uploded by the uder from the view.it is returning null values when i send to the upload function in entity
I think you should read the Documentation about File Upload again. You are correctly using the annotation #ORM\HasLifecycleCallbacks but your code is missing the right annotations for the methods. You can use the #ORM\PostPersist() and #ORM\PostUpdate() annotations to call the upload() function automatically after the request was validated and the object was persisted. Also I would suggest using an entity for each file and creating a OneToMany relation. This will make it more easy and logical to store your files correctly.
Related
Based on: http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
I made an Images Model:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Filesystem\Filesystem;
/**
* #ORM\Entity
* #ORM\Table(name="images")
* #ORM\Entity(repositoryClass="AppBundle\Entity\ImagesRepository")
* #ORM\HasLifecycleCallbacks
*/
class Images
{
/**
* #ORM\Column(type="string", length=60)
* #ORM\Id
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="AppBundle\Doctrine\AutoIdGenerate")
*/
private $id;
/**
* Filename of the Image
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* Filename of the Thumbnail
* #ORM\Column(type="string", length=100)
*/
private $name_small;
/**
* ImageGroup og the Image
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\ImageGroups", inversedBy="images")
*/
private $group;
/**
* #Assert\File(maxSize="20000000")
*/
private $file;
private $upload_dir='images';
private $temp;
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file,$upload_dir)
{
$this->file = $file;
// check if we have an old image path
if (isset($this->name))
{
// store the old name to delete after the update
$this->temp = $this->name;
$this->name = null;
}
else
{
$this->name = sha1(uniqid(mt_rand(), true)).'.'.$file->guessExtension();
}
$this->name_small="small_".$this->name;
$this->upload_dir=$upload_dir;
return $this;
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->getFile())
{
// do whatever you want to generate a unique name
$filename = sha1(uniqid(mt_rand(), true));
$this->name = $filename.'.'.$this->getFile()->guessExtension();
$this->name_small='small_'.$this->name;
}
}
public function getUploadRootDir()
{
return __DIR__.'/../../../../web/'.$this->upload_dir;
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->getFile())
{
return;
}
// if there is an error when moving the file, an exception will
// be automatically thrown by move(). This will properly prevent
// the entity from being persisted to the database on error
//-- Make the Thumbnail Here --
$dir=$this->getUploadRootDir();
echo $dir;
$fs = new Filesystem();
if(!$fs->exists($dir))
{
echo "\nCreating\n";
$fs->mkdir($dir,0777,true);
}
$this->getFile()->move($dir, $this->name);
$file=$dir.'/'.$this->name;
// check if we have an old image
if (isset($this->temp))
{
// delete the old image
unlink($this->getUploadRootDir().'/'.$this->temp);
// clear the temp image path
$this->temp = null;
}
$this->file = null;
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
$file = $this->getAbsolutePath();
if ($file)
{
unlink($file);
}
}
/**
* Get id
*
* #return string
*/
public function getId()
{
return $this->id;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Get nameSmall
*
* #return string
*/
public function getNameSmall()
{
return $this->name_small;
}
/**
* Set group
*
* #param \AppBundle\Entity\ImageGroups $group
*
* #return Images
*/
public function setGroup(\AppBundle\Entity\ImageGroups $group = null)
{
$this->group = $group;
return $this;
}
/**
* Get group
*
* #return \AppBundle\Entity\ImageGroups
*/
public function getGroup()
{
return $this->group;
}
/**
* Set name
*
* #param string $name
*
* #return Images
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Set nameSmall
*
* #param string $nameSmall
*
* #return Images
*/
public function setNameSmall($nameSmall)
{
$this->name_small = $nameSmall;
return $this;
}
}
I also made a Custom Repository In order to do the Uploads:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use AppBundle\Entity\Images;
class ImagesRepository extends EntityRepository
{
public function add($file,$group_id,$user_id)
{
if(empty($group_id)) return -1;
if(empty($user_id)) return -2;
/*Do stuff for uploads*/
/*End of Do stuff for uploads*/
$em = $this->getEntityManager();
$imagesGroups = $em->getRepository('AppBundle:ImageGroups')
->getUserImageGroups($user_id,null,$group_id);
if(empty($imagesGroups) ||(is_int($imagesGroups) && $imagesGroups<0)) return -3; //Null and negative values are false
if(empty($file)) return -4;
$image=new Images();
$image->setFile($file,'images')->setGroup($imagesGroups);
try
{
$em->persist($image);
$em->flush();
return ['id'=>$image->getId(),'image'=>$image->getName(),'thumb'=>$image->getNameSmall()];
}
catch (\Exception $e)
{
echo $e->getMessage();
return false;
}
}
public function delete($user_id,$image_id)
{
if(empty($image_id)) return -1;
if(empty($user_id)) return -2;
$em = $this->getEntityManager();
try
{
$q=$em->createQueryBuilder('i')
->from('AppBundle:Images','i')
->innerJoin('i.group', 'g')
->innerJoin('AppBundle:Users','u')
->select('i')
->where('i.id=:iid')
->andWhere('u.id=:uid')
->setParameter(':uid', $user_id)
->setParameter(':iid', $image_id)
->setMaxResults(1)
->getQuery();
$data=$q->getOneOrNullResult();
if(empty($data)) return -3;
/*Delete Image Stuff*/
/*End Of: Delete Image Stuff*/
$em->remove($data);
$em->flush();
return true;
}
catch (\Exception $e)
{
echo $e->getMessage();
return false;
}
}
}
When I sucessfully do the POST action (I Use curl to test the code above) for some reason I get the following Error:
The file "IMG_20160305_155302.jpg" was not uploaded due to an unknown error.
By echoing the Exception message.
I Originally thought that is a permissions Issues on my filesystem therefore on the Folder /home/pcmagas/Kwdikas/php/apps/symphotest/src/AppBundle/Entity/../../../../web/images
I set the following permissions:
drwxrwxrwx 2 www-data www-data 4096 Μάρ 5 23:02 images
But it does not seem that is the problem. And I wonder what else could it be.
May I have a solution?
Edit 1:
I checked the upload_max_filesize on php.ini and is on 2M and the file that I am Uploading is on 56,0 Kbytes.
The UploadedFile Instance somehow was not setup corectly in controller.
In order to get the file Input I made this class:
<?php
namespace AppBundle\Helpers;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class MyUploadedFile
{
/**
*Storing Uploaded Files
*/
private $files=null;
function __construct($string)
{
if(isset($_FILES[$string]))
{
$files=$_FILES[$string];
if(is_array($files['name']))
{
$this->files=array();
$tmp_files=array();
/**
*Sanitizing When Multiple Files
*/
foreach($files as $key=>$val)
{
foreach($val as $key2=>$val2)
{
$tmp_files[$key2][$key]=$val2;
}
}
foreach($tmp_files as $val)
{
$this->files[]=new UploadedFile($val['tmp_name'],$val['name'],$val['type'],$val['size'],$val['error']);
}
}
elseif(is_string($files['name']))
{
$this->files= new UploadedFile($files['tmp_name'],$files['name'],$files['type'],$files['size'],$files['error']);
}
}
}
/**
*#return {UploadedFile} Or {Array of UploadedFile} or null
*/
public function getFiles()
{
return $this->files;
}
}
?>
Especially in these lines
$this->files= new UploadedFile($files['tmp_name'],$files['name'],$files['type'],$files['size'],$files['error']);
$this->files[]=new UploadedFile($val['tmp_name'],$val['name'],$val['type'],$val['size'],$val['error']);
So when ytou manually make an uploaded file the size comes before the error
So I have a Document Entity that stores file uploads (currently only Images) that is used all over the project which is a blog. Now for the article I want to be able to upload and select an Image that basically has little to no restriction apart from file size, but for a category I want to only be able to use Images that are square or not landscape and not portrait.
The Document Entity looks like this
class Document
{
/**
* #var integer
*/
private $id;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #var string
*/
private $name;
/**
* #var string
*/
private $path;
private $webPath;
private $filename;
/**
* Set name
*
* #param string $name
* #return Document
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set path
*
* #param string $path
* #return Document
*/
public function setPath($path)
{
$this->path = $path;
return $this;
}
/**
* Get path
*
* #return string
*/
public function getPath()
{
return $this->path;
}
public function setFullFilename()
{
$this->filename = $this->id . '.' . $this->path;
}
public function getFilename()
{
return $this->filename;
}
public function getAbsolutePath()
{
//return null === $this->path ? null : $this->getUploadRootDir(). '/' . $this->path;
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->id.'.'.$this->path;
}
public function getWebPath()
{
return null === $this->path ? null : $this->getUploadDir() . '/';
}
public function getUploadRootDir()
{
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
public function getUploadDir()
{
return 'bundles/pgblog/images/uploads';
}
private $file;
private $temp;
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
if(is_file($this->getAbsolutePath()))
{
$this->temp = $this->getAbsolutePath();
}
else {
$this->path = 'initial';
}
}
public function getFile()
{
return $this->file;
}
public function preUpload()
{
if(null !== $this->getFile())
{
$this->path = $this->getFile()->guessExtension();
$this->setMimetype();
$this->setSize();
/*
$filename = sha1(uniqid(mt_rand(), true));
$this->path = $filename . '.' . $this->getFile()->guessExtension();
*/
}
}
public function upload()
{
if(null === $this->getFile())
{
return;
}
if(isset($this->temp))
{
unlink($this->temp);
$this->temp = null;
}
$this->getFile()->move($this->getUploadRootDir(), $this->id . '.' . $this->getFile()->guessExtension());
$this->file = null;
}
public function storeFilenameForRemove()
{
$this->temp = $this->getAbsolutePath();
}
public function removeUpload()
{
if(isset($this->temp))
{
unlink($this->temp);
}
}
public function __toString()
{
return $this->name;
}
/**
* #var string
*/
private $mimetype;
/**
* Set mimetype
*
* #param string $mimetype
* #return Document
*/
public function setMimetype()
{
$this->mimetype = $this->getFile()->getMimeType();
return $this;
}
/**
* Get mimetype
*
* #return string
*/
public function getMimetype()
{
return $this->mimetype;
}
/**
* #var integer
*/
private $size;
/**
* Set size
*
* #param integer $size
* #return Document
*/
public function setSize()
{
$this->size = $this->getFile()->getSize();
return $this;
}
/**
* Get size
*
* #return integer
*/
public function getSize()
{
return $this->size;
}
}
I use seperate forms for file upload and article/category creation. When creating an article or category, a file can be chosen from a list of currently all Files
Here the form type for Article
class ArticleType extends AbstractType
{
...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('text')
->add('tags')
->add('category')
->add('image')
;
}
...
}
and the article entity
class Article
{
...
/**
* Set image
*
* #param \Acme\UtilBundle\Entity\Document $image
* #return Article
*/
public function setImage(\PG\BlogBundle\Entity\Document $image = null)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return \Acme\BlogBundle\Entity\Document
*/
public function getImage()
{
return $this->image;
}
}
Article is related to Document via a unidirectional many to one relationship
manyToOne:
image:
targetEntity: Document
joinColumn:
name: document_id
referencedColumnName: id
So in the form the files can be set by a select. For the article this is just fine, since I want to be able to use any image as an attachment, but for the categories I want only the files that are square as I said earlier.
I thought about using validation for the category image, but since the Image is selected by a select, the actual data is just a string (the file name given on the upload form) and not the image it self so the validation returns the error
Catchable Fatal Error: Argument 1 passed to Acme\BlogBundle\Entity\Category::setImage() must be an instance of Acme\BlogBundle\Entity\Document, string given...
So my question is, how do I restrict the Image options in the category form to only square images and how do I validate this properly?
The reason I only want to use square Images for category is so I can display a nice symmetric list of all the cateogries by the way.
Thanks in advance!
Store the image dimensions in your document entity (length, width).
That way when fetching the document collection for category forms you can filter image documents where length == width, so only display appropriate documents.
For validation, you have a number of options, the best place to start would be here. In your place I'd look into validation groups.
I have working entity References.php including Image, but I don't know how to in Symfony2 delete old image saved in this reference (if exists) and create new. Because now, it didn't delete current image, so only created a new and set new image_path into this entity. Here is my try to delete it on preUpload method but it set current file to NULL and then nothing (so I have error - You have to choose a file)
<?php
namespace Acme\ReferenceBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Entity(repositoryClass="Acme\ReferenceBundle\Entity\ReferenceRepository")
* #ORM\Table(name="`references`")
* #ORM\HasLifecycleCallbacks
*/
class Reference
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=200)
* #Assert\NotBlank(
* message = "Name cannot be blank"
* )
* #Assert\Length(
* min = "3",
* minMessage = "Name is too short"
* )
*/
private $name;
/**
* #ORM\Column(type="string", length=200)
* #Assert\NotBlank(
* message = "Description cannot be blank"
* )
* #Assert\Length(
* min = "3",
* minMessage = "Description is too short"
* )
*/
private $description;
/**
* #ORM\Column(type="string", length=200)
* #Assert\Url(
* message = "URL is not valid"
* )
*/
private $url;
/**
* #ORM\ManyToMany(targetEntity="Material", inversedBy="references")
* #Assert\Count(min = 1, minMessage = "Choose any material")
*/
private $materials;
/**
* #ORM\Column(type="text", length=255, nullable=false)
* #Assert\NotNull(
* message = "You have to choose a file"
* )
*/
private $image_path;
/**
* #Assert\File(
* maxSize = "5M",
* mimeTypes = {"image/jpeg", "image/gif", "image/png", "image/tiff"},
* maxSizeMessage = "Max size of file is 5MB.",
* mimeTypesMessage = "There are only allowed jpeg, gif, png and tiff images"
* )
*/
private $file;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Reference
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set description
*
* #param string $description
* #return Reference
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set url
*
* #param string $url
* #return Reference
*/
public function setUrl($url)
{
$this->url = $url;
return $this;
}
/**
* Get url
*
* #return string
*/
public function getUrl()
{
return $this->url;
}
/**
* Set materials
*
* #param string $materials
* #return Reference
*/
public function setMaterials($materials)
{
$this->materials = $materials;
return $this;
}
/**
* Get materials
*
* #return string
*/
public function getMaterials()
{
return $this->materials;
}
/**
* Set image_path
*
* #param string $imagePath
* #return Reference
*/
public function setImagePath($imagePath)
{
$this->image_path = $imagePath;
return $this;
}
/**
* Get image_path
*
* #return string
*/
public function getImagePath()
{
return $this->image_path;
}
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
/**
* Called before saving the entity
*
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
$oldImage = $this->image_path;
$oldImagePath = $this->getUploadRootDir().'/'.$oldImage;
if (null !== $this->file) {
if($oldImage && file_exists($oldImagePath)) unlink($oldImagePath); // not working correctly
$filename = sha1(uniqid(mt_rand(), true));
$this->image_path = $filename.'.'.$this->file->guessExtension();
}
}
/**
* Called before entity removal
*
* #ORM\PostRemove()
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath()) {
unlink($file);
}
}
/**
* Called after entity persistence
*
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->file) {
return;
}
// use the original file name here but you should
// sanitize it at least to avoid any security issues
// move takes the target directory and then the
// target filename to move to
$this->file->move(
$this->getUploadRootDir(),
$this->image_path
);
// set the path property to the filename where you've saved the file
$this->image_path = $this->file->getClientOriginalName();
// clean up the file property as you won't need it anymore
$this->file = null;
}
protected function getAbsolutePath()
{
return null === $this->image_path
? null
: $this->getUploadRootDir().'/'.$this->image_path;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded
// documents should be saved
return __DIR__.'/../../../../'.$this->getUploadDir();
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw up
// when displaying uploaded doc/image in the view.
return 'uploads/references';
}
public function getWebPath()
{
return $this->getUploadDir().'/'.$this->image_path;
}
/**
* Constructor
*/
public function __construct()
{
$this->materials = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add materials
*
* #param \Acme\ReferenceBundle\Entity\Material $materials
* #return Reference
*/
public function addMaterial(\Acme\ReferenceBundle\Entity\Material $materials)
{
$this->materials[] = $materials;
return $this;
}
/**
* Remove materials
*
* #param \Acme\ReferenceBundle\Entity\Material $materials
*/
public function removeMaterial(\Acme\ReferenceBundle\Entity\Material $materials)
{
$this->materials->removeElement($materials);
}
}
Any idea?
So I found solution. For first, I had to create an Assert callback for File Uploading, because I was using NotNull() Assert for Reference entity. So if I selected any file and sent form, I was always getting error You have to choose a file. So my first edit was here:
use Symfony\Component\Validator\ExecutionContextInterface; // <-- here
/**
* #ORM\Entity(repositoryClass="Acme\ReferenceBundle\Entity\ReferenceRepository")
* #ORM\Table(name="`references`")
* #ORM\HasLifecycleCallbacks
* #Assert\Callback(methods={"isFileUploadedOrExists"}) <--- and here
*/
class Reference
{
// code
}
and then in my code add a new method:
public function isFileUploadedOrExists(ExecutionContextInterface $context)
{
if(null === $this->image_path && null === $this->file)
$context->addViolationAt('file', 'You have to choose a file', array(), null);
}
Also I deleted NotNull assertion in my $image_path property.
Then it was working successfuly - if I selected a file and submitted the form, reference was created with image. But it wasn't finished yet. There was my problem which I asked in this question - delete old image and create a new image with new path, of course.
After many experiments, i found the working and good looking solution. In my controller, I added one variable before form validation and after it is used to delete old image:
$oldImagePath = $reference->getImagePath(); // get path of old image
if($form->isValid())
{
if ($form->get('file')->getData() !== null) { // if any file was updated
$file = $form->get('file')->getData();
$reference->removeFile($oldImagePath); // remove old file, see this at the bottom
$reference->setImagePath($file->getClientOriginalName()); // set Image Path because preUpload and upload method will not be called if any doctrine entity will not be changed. It tooks me long time to learn it too.
}
$em->persist($reference);
try {
$em->flush();
} catch (\PDOException $e) {
//sth
}
And my removeFile() method:
public function removeFile($file)
{
$file_path = $this->getUploadRootDir().'/'.$file;
if(file_exists($file_path)) unlink($file_path);
}
And at the end, I deleted $this->image_path = $this->file->getClientOriginalName(); line in upload() method because it causes a problem with preview image in the form, if you use any. It sets an original file name as path, but if you reload page, you will see the real path of image. Removing this line will fix the problem.
Thanks everyone to posting answers, who helps me to find the solution.
If the image_path is already set there is an "old" image you want to replace.
Inside your upload() method instead of ...
// set the path property to the filename where you've saved the file
$this->image_path = $this->file->getClientOriginalName();
... check for existance of a previous file and remove it before:
if ($this->image_path) {
if ($file = $this->getAbsolutePath()) {
unlink($file);
}
}
$this->image_path = $this->file->getClientOriginalName();
the #Assert\NotNull on the image_path property is tested before your PrePersist/PreUpdate method, so the form validation is not happy because image_path is only provided in the entity internal, the request does not provide the form with "image_path" property, I think you should remove this Assert which is not really useful I think since it is not linked to a form.
OR
your old image_path is the fresh one, and not the old one because it is processed after form binding.
You should use event listeners, which are way better then annotation events in entities, so that you will be able in preUpdate event to retrieve the right values.
You could the use methods like these:
hasChangedField($fieldName) to check if the given field name of the current entity changed.
getOldValue($fieldName) and getNewValue($fieldName) to access the values of a field.
setNewValue($fieldName, $value) to change the value of a field to be updated.
I'm creating a ticket system for practice purposes.
Right now I'm having a problem with uploading files.
The idea is that a ticket can have multiple attachments, so I created a many-to-one relationship between the ticket and the upload tables.
class Ticket {
// snip
/**
* #ORM\OneToMany(targetEntity="Ticket", mappedBy="ticket")
*/
protected $uploads;
// snip
}
The upload entity class contains the upload functionality, which I took from this tutorial:
<?php
namespace Sytzeandreae\TicketsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Entity(repositoryClass="Sytzeandreae\TicketsBundle\Repository\UploadRepository")
* #ORM\Table(name="upload")
* #ORM\HasLifecycleCallbacks
*/
class Upload
{
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
private $temp;
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Ticket", inversedBy="upload")
* #ORM\JoinColumn(name="ticket_id", referencedColumnName="id")
*/
protected $ticket;
/**
* #ORM\Column(type="string")
*/
protected $title;
/**
* #ORM\Column(type="string")
*/
protected $src;
public function getAbsolutePath()
{
return null === $this->src
? null
: $this->getUploadRootDir().'/'.$this->src;
}
public function getWebPath()
{
return null === $this->src
? null
: $this->getUploadDir().'/'.$this->src;
}
public function getUploadRootDir()
{
// The absolute directory path where uplaoded documents should be saved
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
public function getUploadDir()
{
// Get rid of the __DIR__ so it doesn/t screw up when displaying uploaded doc/img in the view
return 'uploads/documents';
}
/**
* Sets file
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
if (isset($this->path)) {
// store the old name to delete after the update
$this->temp = $this->path;
$this->path = null;
} else {
$this->path = 'initial';
}
}
/**
* Get file
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set src
*
* #param string $src
*/
public function setSrc($src)
{
$this->src = $src;
}
/**
* Get src
*
* #return string
*/
public function getSrc()
{
return $this->src;
}
/**
* Set ticket
*
* #param Sytzeandreae\TicketsBundle\Entity\Ticket $ticket
*/
public function setTicket(\Sytzeandreae\TicketsBundle\Entity\Ticket $ticket)
{
$this->ticket = $ticket;
}
/**
* Get ticket
*
* #return Sytzeandreae\TicketsBundle\Entity\Ticket
*/
public function getTicket()
{
return $this->ticket;
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->getFile()) {
// do whatever you want to generate a unique name
$filename = sha1(uniqid(mt_rand(), true));
$this->src = $filename.'.'.$this->getFile()->guessExtension();
}
}
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->getFile()) {
return;
}
// move takes the target directory and then the target filename to move to
$this->getFile()->move(
$this->getUploadRootDir(),
$this->src
);
// check if we have an old image
if (isset($this->temp)) {
// delete the old image
unlink($this->getUploadRootDir().'/'.$this->temp);
// clear the temp image path
$this->temp = null;
}
// clean up the file property as you won't need it anymore
$this->file = null;
}
/**
* #ORM\PostRemove
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath()) {
unlink($file);
}
}
}
The form is build as follows:
class TicketType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('title')
->add('description')
->add('priority')
->add('uploads', new UploadType())
}
Where UploadType looks like this:
class UploadType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options) {
$builder->add('file', 'file', array(
'label' => 'Attachments',
'required' => FALSE,
'attr' => array (
'accept' => 'image/*',
'multiple' => 'multiple'
)
));
}
This part seems to work fine, I do get presented a form which contains a file uploader.
Once I put this line in the Ticket entity's constuctor:
$this->uploads = new \Doctrine\Common\Collections\ArrayCollection();
It throws me the following error:
Neither property "file" nor method "getFile()" nor method "isFile()"
exists in class "Doctrine\Common\Collections\ArrayCollection"
If I leave this line out, and upload a file, it throws me the following error:
"Sytzeandreae\TicketsBundle\Entity\Ticket". Maybe you should create
the method "setUploads()"?
So next thing I did was creating this method, try an upload again and now it throws me:
Class Symfony\Component\HttpFoundation\File\UploadedFile is not a
valid entity or mapped super class.
This is where I am really stuck. I fail to see what, at what stage, I did wrong and am hoping for some help :)
Thanks!
You can either add a collection field-type of UploadType() to your form. Notice that you can't use the multiple option with your current Upload entity ... but it's the quickest solution.
Or adapt your Ticket entity to be able to handle multiple files in an ArrayCollection and looping over them.
In my Restful API, i want to upload a file in one call.
In my tests, the form is initialized and binded at the same time, but all my data fields form are empty and the result is an empty record in my database.
If I pass by the form view and then submit it, all is fine but i want to call the Webservice in one call. The webservice is destinated to be consumed by a backbone app.
Thanks for your help.
My Test:
$client = static::createClient();
$photo = new UploadedFile(
'/Userdirectory/test.jpg',
'photo.jpg',
'image/jpeg',
14415
);
$crawler = $client->request('POST', '/ws/upload/mydirectory', array(), array('form[file]' => $photo), array('Content-Type'=>'multipart/formdata'));
There is my controller action:
public function uploadAction(Request $request, $directory, $_format)
{
$document = new Media();
$document->setDirectory($directory);
$form = $this->createFormBuilder($document, array('csrf_protection' => false))
/*->add('directory', 'hidden', array(
'data' => $directory
))*/
->add('file')
->getForm()
;
if ($this->getRequest()->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($document);
$em->flush();
if($document->getId() !== '')
return $this->redirect($this->generateUrl('media_show', array('id'=>$document->getId(), 'format'=>$_format)));
}else{
$response = new Response(serialize($form->getErrors()), 406);
return $response;
}
}
return array('form' => $form->createView());
}
My Media Entity:
<?php
namespace MyRestBundle\RestBundle\Entity;
use Symfony\Component\Serializer\Normalizer\NormalizableInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Media
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $path;
public $directory;
/**
* #Assert\File(maxSize="6000000")
*/
public $file;
/**
* #see \Symfony\Component\Serializer\Normalizer\NormalizableInterface
*/
function normalize(NormalizerInterface $normalizer, $format= null)
{
return array(
'path' => $this->getPath()
);
}
/**
* #see
*/
function denormalize(NormalizerInterface $normalizer, $data, $format = null)
{
if (isset($data['path']))
{
$this->setPath($data['path']);
}
}
protected function getAbsolutePath()
{
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->path;
}
protected function getWebPath()
{
return null === $this->path ? null : $this->getUploadDir().'/'.$this->path;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded documents should be saved
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw when displaying uploaded doc/image in the view.
return 'uploads/'.(null === $this->directory ? 'documents' : $this->directory);
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
// do whatever you want to generate a unique name
$this->path = $this->getUploadDir().'/'.sha1(uniqid(mt_rand(), true)).'.'.$this->file->guessExtension();
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
// if there is an error when moving the file, an exception will
// be automatically thrown by move(). This will properly prevent
// the entity from being persisted to the database on error
$this->file->move($this->getUploadRootDir(), $this->path);
unset($this->file);
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath()) {
unlink($file);
}
}
/**
* Set Directory
*
* #param string $directory
* #return Media
*/
public function setDirectory($directory)
{
$this->directory = $directory;
return $this;
}
/**
* Set Path
*
* #param string $path
* #return Media
*/
public function setPath($path)
{
$this->path = $path;
return $this;
}
/**
* Get path
*
* #return string
*/
public function getPath()
{
$request = Request::createFromGlobals();
return $request->getHost().'/'.$this->path;
}
/**
* Get id
*
* #return string
*/
public function getId()
{
return $this->id;
}
}
My routing:
upload_dir_media:
pattern: /upload/{directory}.{_format}
defaults: { _controller: MyRestBundle:Media:upload, _format: html }
requirements: { _method: POST }
Try breaking this problem down into a simple state. How would you post 'text' or a variable to a web service with one post? Because an image is just a long string. Check out the php function imagecreatefromstring or imgtostring . This is often what goes on behind the scenes of you image transfer protocols.. Once you so solve the simpler problem, you will have proven you can solve your original problem.