upload file in symfony2 with my own form - php

I'm trying to upload a file using my own form with symfony2.
I get no errors, and the name of the file is correctly addeded on database, but the file is not uploaded.
here is my routing.yml :
upload_homepage:
pattern: /upload
defaults: { _controller: UploadBundle:Default:index }
upload:
pattern: /upload/file
defaults: { _controller: UploadBundle:Default:upload }
twig:
<form enctype="multipart/form-data" action="{{ path('upload') }}" method="POST">
<input type="file" name="file">
<input type="submit">
</form>
My controller :
<?php
namespace Upload\UploadBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Upload\UploadBundle\Entity\Document;
class DefaultController extends Controller
{
public function indexAction()
{
return $this->render('UploadBundle:Default:index.html.twig');
}
public function uploadAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$document = new Document();
$document->setChemain($request->files->get('file')->getClientOriginalName());
//$document->upload();
// print_r($request->files->get('file')->getClientOriginalName());
// die();
$em->persist($document);
$em->flush();
return new Response("Ok");
}
}
The Entity:
<?php
namespace Upload\UploadBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
// use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* Document
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Upload\UploadBundle\Entity\DocumentRepository")
* #ORM\HasLifecycleCallbacks
*/
class Document
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="chemain", type="string", length=255)
*/
private $chemain;
/**
* #Assert\File(maxSize="6000000")
* mimeTypes = {"image/jpg", "image/gif", "image/png", "image/tiff"},
* maxSizeMessage = "The maxmimum allowed file size is 5MB.",
* mimeTypesMessage = "Only the filetypes image are allowed."
*/
public $file;
/**
* Get id
*
* #return integer
*/
// propriété utilisé temporairement pour la suppression
private $filenameForRemove;
public function getId()
{
return $this->id;
}
/**
* Set chemain
*
* #param string $chemain
* #return Document
*/
public function setChemain($chemain)
{
$this->chemain = $chemain;
return $this;
}
/**
* Get chemain
*
* #return string
*/
public function getChemain()
{
return $this->chemain;
}
public function getWebPath()
{
return null === $this->chemain ? null : $this->getUploadDir().'/'.$this->chemain;
}
protected function getUploadRootDir()
{
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
return 'uploads';
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
$this->chemain = $this->file->guessExtension();
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
try {
$this->file->move($this->getUploadRootDir(), $this->id.'.'.$this->file->guessExtension());
unset($this->file);
} catch (FileException $e) {
return $e;
}
}
/**
* #ORM\PreRemove()
*/
public function storeFilenameForRemove()
{
$this->filenameForRemove = $this->getAbsolutePath();
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
if ($this->filenameForRemove) {
unlink($this->filenameForRemove);
}
}
public function getAbsolutePath()
{
return null === $this->chemain ? null : $this->getUploadRootDir().'/'.$this->id.'.'.$this->chemain;
}
}
Any idea ?

I find a solution, it's work, but I don't know if is the correctly way to do this.
the solution : I modify just the function uploadAction
public function uploadAction(Request $request)
{
$document = new Document();
$form = $this->createFormBuilder($document)
->add('file')
->getForm();
if ($this->getRequest()->isMethod('POST')) {
$form->bind($this->getRequest());
$em = $this->getDoctrine()->getManager();
$em->persist($document);
$em->flush();
return new Response("Ok");
}
return new Response("No");
}

You must uncoment upload line
namespace Upload\UploadBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Upload\UploadBundle\Entity\Document;
class DefaultController extends Controller
{
public function indexAction()
{
return $this->render('UploadBundle:Default:index.html.twig');
}
public function uploadAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$document = new Document();
$document->upload();
$em->persist($document);
$em->flush();
return new Response("Ok");
}
}
And at yours document entity add upload method
/**
* #param $varfile
*
* #return $this
*/
public function setFile($file = null)
{
$this->file = $file;
return $this;
}
/**
* #return string $file
*/
public function getFile()
{
return $this->file;
}
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->getFile()) {
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->getFile()->move(
$this->getUploadRootDir(),
$this->getFile()->getClientOriginalName()
);
// set the path property to the filename where you've saved the file
$this->file = $this->getFile()->getClientOriginalName();
$this->chemain = $this->getFile()->getClientOriginalName();
}
/**
* #return null|string
*/
public function getAbsolutePath()
{
if($this->file)
{
return $this->getUploadRootDir().'/'.$this->file;
}
return null;
}
/**
* #return null|string
*/
public function getWebPath()
{
if($this->file)
{
return '/web/'.$this->getUploadDir().'/'.$this->file;
}
return null;
}

Related

Symfony maintaining relative path for file uploads

I'm using Symfony 4.1, and I had a hard time getting the relative/fullpath to work as I want it.
In my database, I have a Customer entity with an attribute called photo.
<?php
namespace App\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Entity\CustomerRepository")
* #ORM\Table("Customer")
*/
class Customer {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", nullable=true)
*
* #Assert\File(mimeTypes={ "image/png","image/jpeg" })
*/
private $photo;
public function getPhoto(): ?string
{
return $this->photo;
}
public function setPhoto(?string $photo): self
{
$this->photo = $photo;
return $this;
}
This makes sense and when I save the Customer with a Photo upload, it saves the photo in the database and on the filesystem as I expect.
In the database, the photo column will be set to something like '010925c8c427bddca9020197212b64af.png'
That's what I want, so it's all good.
The problem came up when I was trying to update an existing Customer entity. Customer->getPhoto() will return the relative path file name '010925c8c427bddca9020197212b64af.png.'
But the form doesn't pass validation, it says that this file doesn't exist.
$em = $this->getDoctrine()->getManager();
$custRepo = $em->getRepository('App:Customer');
$customer = $custRepo->findOneById($id);
$custForm = $this->createForm(CustomerType::class, $customer);
$custForm->handleRequest($request);
if ($custForm->isSubmitted() && $custForm->isValid()) {
$em->flush();
}
It fails because the validation doesn't look in the photos directory.
Here's my solution, which does work, but it seems too hackish. I wasn't wondering if someone had a more elegant approach to this.
$em = $this->getDoctrine()->getManager();
$custRepo = $em->getRepository('App:Customer');
$customer = $custRepo->findOneById($id);
$customer->setPhoto(new File($this->getParameter('photos_dir') .'/' . $customer->getPhoto()));
$custForm = $this->createForm(CustomerType::class, $customer);
$custForm->handleRequest($request);
if ($custForm->isSubmitted() && $custForm->isValid()) {
$photoPathParts = explode('/', $customer->getPhoto());
$customer->setPhoto(array_pop($photoPathParts));
$em->flush();
}
I'm getting the fullpath for the photo and updating the entity I'm currently work on. That gets the form validation to pass, but if I just save it, the path in the db is updated with the full path to the photo. That's not what I want, so I reset the photo to the relative path filename.
/**
* #ORM\Column(type="string", nullable=true)
*
* #Assert\File(mimeTypes={ "image/png","image/jpeg" })
*/
private $photo;
Look at this example how to upload image. The image is in a separate entity , you can relate it to customer OneToOne.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* Image
*
* #ORM\Table(name="image")
* #ORM\Entity(repositoryClass="App\Repository\ImageRepository")
* #ORM\HasLifecycleCallbacks
*/
class Image
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(name="extension", type="string", length=180)
*/
private $name;
/**
* #Assert\Image()
*/
public $file;
private $tempFilename;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function setFile(UploadedFile $file)
{
$this->file = $file;
if (null !== $this->extension) {
$this->tempFilename = $this->name;
$this->extension = null;
$this->name = null;
}
}
public function getFile()
{
return $this->file;
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null === $this->file) {
return;
}
$extension = $this->file->guessExtension();
$this->name = md5(uniqid('', true)) . '.' . $extension;
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
if (null !== $this->tempFilename) {
$oldFile = $this->getUploadRootDir() . '/' . $this->tempFilename;
if (file_exists($oldFile)) {
unlink($oldFile);
}
}
$this->file->move($this->getUploadRootDir(), $this->name);
}
/**
* #ORM\PreRemove()
*/
public function preRemoveUpload()
{
$this->tempFilename = $this->getUploadRootDir() . '/' . $this->name;
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
if (file_exists($this->tempFilename)) {
unlink($this->tempFilename);
}
}
//folder
public function getUploadDir()
{
return 'uploads/photos';
}
// path to folder web
protected function getUploadRootDir()
{
return __DIR__ . '/../../public/' . $this->getUploadDir();
}
public function getWebPath()
{
return $this->getUploadDir() . '/' . $this->getName();
}
}
ImageFormType
NB: You should use the public attribue $file in the formType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file', FileType::class, array(
'label'=> false,
))
;
}

Symfony3, Edit Form for collection of images

I was following these guides: File upload tutorial and Collection form type guide. Everything was ok but "edit" action. Collection of images is shown correctly in "edit" action but when I try to submit the form (without adding any new images) i get an error.
Call to a member function guessExtension() on null
Which means that images in a collection got null values, instead of UploadedFile.
I've searched a lot and read many of sof questions about handling collection of images with doctrine but still no clue.
Questions like:
Symfony2, Edit Form, Upload Picture
howto handle edit forms with FileType inputs in symfony2
Multiple (oneToMany) Entities form generation with symfony2 and file upload
So the question is how can u handle edit images? Removal is needed too.
Controller edit action
public function editAction(Request $request, Stamp $stamp)
{
if (!$stamp) {
throw $this->createNotFoundException('No stamp found');
}
$form = $this->createForm(AdminStampForm::class, $stamp);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** #var Stamp $stamp */
$stamp = $form->getData();
foreach ($stamp->getImages() as $image) {
$fileName = md5(uniqid()) . '.' . $image->getName()->guessExtension();
/** #var $image StampImage */
$image->getName()->move(
$this->getParameter('stamps_images_directory'),
$fileName
);
$image->setName($fileName)->setFileName($fileName);
}
$em = $this->getDoctrine()->getManager();
$em->persist($stamp);
$em->flush();
$this->addFlash('success', 'Successfully edited a stamp!');
return $this->redirectToRoute('admin_stamps_list');
}
return [
'form' => $form->createView(),
];
}
Entity
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\JoinTable;
use Doctrine\ORM\Mapping\ManyToMany;
/**
* Class Stamp
* #package AppBundle\Entity
*
*
* #ORM\Entity(repositoryClass="AppBundle\Repository\StampRepository")
* #ORM\Table(name="stamp")
* #ORM\HasLifecycleCallbacks()
*/
class Stamp
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
// * #ORM\OneToMany(targetEntity="AppBundle\Entity\StampImage", mappedBy="id", cascade={"persist", "remove"})
/**
* #ManyToMany(targetEntity="AppBundle\Entity\StampImage", cascade={"persist"})
* #JoinTable(name="stamps_images",
* joinColumns={#JoinColumn(name="stamp_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="image_id", referencedColumnName="id", unique=true)}
* ) */
private $images;
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #ORM\Column(type="datetime")
*/
private $updatedAt;
public function __construct()
{
$this->images = new ArrayCollection();
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
public function addImage(StampImage $image)
{
$this->images->add($image);
}
public function removeImage(StampImage $image)
{
$this->images->removeElement($image);
}
/**
* #return mixed
*/
public function getImages()
{
return $this->images;
}
/**
* #param mixed $images
*
* #return $this
*/
public function setImages($images)
{
$this->images = $images;
return $this;
}
/**
* #return mixed
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* #param mixed $createdAt
*
* #return Stamp
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* #return mixed
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* #param mixed $updatedAt
*
* #return Stamp
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function updateTimestamps()
{
$this->setUpdatedAt(new \DateTime('now'));
if (null == $this->getCreatedAt()) {
$this->setCreatedAt(new \DateTime());
}
}
}
StampImage entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity as UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class StampImage
* #package AppBundle\Entity
*
* #ORM\Entity()
* #ORM\Table(name="stamp_image")
* #UniqueEntity(fields={"name"}, message="This file name is already used.")
*/
class StampImage
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
private $name;
/**
* #ORM\Column(type="string")
*/
private $fileName;
/**
* #return mixed
*/
public function getFileName()
{
return $this->fileName;
}
/**
* #param mixed $fileName
*
* #return StampImage
*/
public function setFileName($fileName)
{
$this->fileName = $fileName;
return $this;
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*
* #return StampImage
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
}
Main entity form
<?php
namespace AppBundle\Form;
use AppBundle\Entity\Stamp;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AdminStampForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('images', CollectionType::class, [
'entry_type' => AdminStampImageForm::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Stamp::class,
]);
}
public function getName()
{
return 'app_bundle_admin_stamp_form';
}
}
Image form type
<?php
namespace AppBundle\Form;
use AppBundle\Entity\StampImage;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AdminStampImageForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', FileType::class, [
'image_name' => 'fileName',
'label' => false,
'attr' => [
],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => StampImage::class,
'required' => false
]);
}
public function getBlockPrefix()
{
return 'app_bundle_admin_stamp_image_form';
}
}
FileType extension
- extends 'form_div_layout.html.twig'
- block file_widget
- spaceless
- if image_url is not null
%img{:src => "#{image_url}"}
%div{:style => "display: none;"}
#{block('form_widget')}
- else
#{block('form_widget')}
1) Multiupload (but not OneToMany).
2) For editing an uploaded image:
# AppBundle/Entity/Stamp
/**
* #ORM\Column(type="string", length=255)
*/
private $image;
# AppBundle/Form/StampForm
->add('imageFile', FileType::class, [ //the $image property of Stamp entity class will store the path to the file, and this imageFile field will get the uploaded file
'data_class' => null, //important!
])
#AppBundle/Controller/StampController
/**
* #Route("/{id}/edit", name="stamp_edit")
* #Method({"GET", "POST"})
*/
public function editAction(Request $request, Stamp $stamp) {
$editForm = $this->createForm(StampType::class, $stamp, [
'action'=>$this->generateUrl('stamp_edit',['id'=>$stamp->getId()]),
'method'=>'POST'
]);
$editForm->handleRequest($request);
if($editForm->isSubmitted() && $form->isValid()) {
$imageFile = $editForm->get('imageFile')->getData();
if (null != $imageFile) { //this means that for the current record that needs to be edited, the user has chosen a different image
//1. remove the old image
$oldImg = $this->getDoctrine()->getRepository('AppBundle:Stamp')->find($stamp);
$this->get('app.file_remove')->removeFile($oldImg->getImage());
//2. upload the new image
$img = $this->get('app.file_upload')->upload($imageFile);
//3. update the db, replacing the path to the old file with the path to the new uploaded file
$stamp->setImage($img);
$this->getDoctrine()->getManager()->flush();
//4. add a success flash, and anything else you need, and redirect to a route
} else { //if the user has chosen to edit a different field (but not the image one)
$this->getDoctrine()->getManager()->flush();
//add flash message, and redirect to a route
}
}
return $this->render(...);
}
#AppBundle/Services/FileRemove
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
class FileRemove {
private $targetDir;
public function __construct($targetDir) {
$this->targetDir = $targetDir;
}
public function removeFile($path) {
$fs = new Filesystem();
$file = $this->targetDir . '/' . $path;
try{
if($fs->exists($file)){
$fs->remove($file);
return true;
}
return false;
} catch(IOExceptionInterface $e){
//log error for $e->getPath();
}
}
}
#app/config/services.yml
app.file_remove:
class: AppBundle/Services/FileRemove
arguments: ['%stamp_dir%']
#app/config/config.yml
parameters:
stamp_dir: '%kernel.root_dir%/../web/uploads/stamps' //assuming this is how you've set up the upload directory
#AppBundle/Services/FileUpload
use Symfony\Component\HttpFoundation\File\UploadedFile;
class FileUpload{
private $targetDir;
public function __construct($targetDir) {
$this->targetDir = $targetDir;
}
public function upload(UploadedFile $file) {
$file_name = empty($file->getClientOriginalName()) ? md5(uniqid()).'.'.$file->guessExtension() : $file->getClientOriginalName();
$file->move($this->targetDir, $file_name);
return $file_name;
}
}
#app/config/services.yml
app.file_upload:
class: AppBundle\Services\FileUpload
arguments: ['%stamp_dir%']
Sorry for typos, and please let me know if this works for your case, as I adapted my case to yours.
Check you have uploaded image vie form or not before foreach.
if ($form->isSubmitted() && $form->isValid()) {
/** #var Stamp $stamp */
$stamp = $form->getData();
if(!empty($stamp->getImages()) && count($stamp->getImages()) > 0){ // Check uploaded image
foreach ($stamp->getImages() as $image) {
$fileName = md5(uniqid()) . '.' . $image->guessExtension();
/** #var $image StampImage */
$image->getName()->move(
$this->getParameter('stamps_images_directory'),
$fileName
);
$image->setName($fileName)->setFileName($fileName);
}
}
$em = $this->getDoctrine()->getManager();
$em->persist($stamp);
$em->flush();
$this->addFlash('success', 'Successfully edited a stamp!');
return $this->redirectToRoute('admin_stamps_list');
}
Update #1
Replace
$fileName = md5(uniqid()) . '.' . $image->getName()->guessExtension();
with
$fileName = md5(uniqid()) . '.' . $image->guessExtension();

Symfony 3.0 Error: The file "IMG_20160305_155302.jpg" was not uploaded due to an unknown error

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

Updating a user profile with an uploaded image in Symfony2

I have a user profile that allows a user to upload and save a profile picture.
I have a UserProfile entity and a Document entity:
Entity/UserProfile.php
namespace Acme\AppBundle\Entity\Profile;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="ISUserProfile")
*/
class UserProfile extends GenericProfile
{
/**
* #ORM\OneToOne(cascade={"persist", "remove"}, targetEntity="Acme\AppBundle\Entity\Document")
* #ORM\JoinColumn(name="picture_id", referencedColumnName="id", onDelete="set null")
*/
protected $picture;
/**
* Set picture
*
* #param Acme\AppBundle\Entity\Document $picture
*/
public function setPicture($picture)
{
$this->picture = $picture;
}
/**
* Get picture
*
* #return Acme\AppBundle\Entity\Document
*/
public function getPicture()
{
return $this->picture;
}
}
Entity/Document.php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Document
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $path;
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
public function getAbsolutePath()
{
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->path;
}
public 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/documents';
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
$this->path = sha1(uniqid(mt_rand(), true)).'.'.$this->file->guessExtension();
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
$this->file->move($this->getUploadRootDir(), $this->path);
unset($this->file);
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath()) {
unlink($file);
}
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set path
*
* #param string $path
*/
public function setPath($path)
{
$this->path = $path;
}
/**
* Get path
*
* #return string
*/
public function getPath()
{
return $this->path;
}
/**
* Set file
*
* #param string $file
*/
public function setFile($file)
{
$this->file = $file;
}
/**
* Get file
*
* #return string
*/
public function getFile()
{
return $this->file;
}
}
My user profile form type adds a document form type to include the file uploader on the user profile page:
Form/UserProfileType.php
namespace Acme\AppBundle\Form;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UserProfileType extends GeneralContactType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
/*
if(!($pic=$builder->getData()->getPicture()) || $pic->getWebPath()==''){
$builder->add('picture', new DocumentType());
}
*/
$builder
->add('picture', new DocumentType());
//and add some other stuff like name, phone number, etc
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\AppBundle\Entity\Profile\UserProfile',
'intention' => 'user_picture',
'cascade_validation' => true,
));
}
public function getName()
{
return 'user_profile_form';
}
}
Form/DocumentType.php
namespace Acme\AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DocumentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\AppBundle\Entity\Document',
));
}
public function getName()
{
return 'document_form';
}
}
In my controller I have an update profile action:
Controller/ProfileController.php
namespace Acme\AppBundle\Controller;
class AccountManagementController extends BaseController
{
/**
* #param Symfony\Component\HttpFoundation\Request
* #return Symfony\Component\HttpFoundation\Response
*/
public function userProfileAction(Request $request) {
$user = $this->getCurrentUser();
$entityManager = $this->getDoctrine()->getEntityManager();
if($user && !($userProfile = $user->getUserProfile())){
$userProfile = new UserProfile();
$userProfile->setUser($user);
}
$uploadedFile = $request->files->get('user_profile_form');
if ($uploadedFile['picture']['file'] != NULL) {
$userProfile->setPicture(NULL);
}
$userProfileForm = $this->createForm(new UserProfileType(), $userProfile);
if ($request->getMethod() == 'POST') {
$userProfileForm->bindRequest($request);
if ($userProfileForm->isValid()) {
$entityManager->persist($userProfile);
$entityManager->flush();
$this->get('session')->setFlash('notice', 'Your user profile was successfully updated.');
return $this->redirect($this->get('router')->generate($request->get('_route')));
} else {
$this->get('session')->setFlash('error', 'There was an error while updating your user profile.');
}
}
$bindings = array(
'user_profile_form' => $userProfileForm->createView(),
);
return $this->render('user-profile-template.html.twig', $bindings);
}
}
Now, this code works... but it's ugly as hell. why do I have to check the request object for an uploaded file and set the picture to null so Symfony realises that it needs to persist a new Document entity?
Surely having a simple user profile page with the option to upload an image as a profile picture should be simpler than this?
What am I missing??
(I don´t think you expect some feedback now, after some months, but maybe it helps other people with the same issue)
I have a similar setup and goal, found this post and after copying some of the code I found out that I don´t need the "$uploadedFile"-snippet that you refer to as "ugly". I override the fos registration form and all I need to add is a DocumentType.php and a RegistrationFormType.php (like your user UserProfileType).
I can´t tell why your version needs the ugly bit of code. (or maybe I will run into the same issue when I´m coding an Update form?).

How to upload a file in one webservice call with Symfony2?

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.

Categories