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.
Related
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();
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;
}
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.
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.
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?).