I implemented a form for upload a file in a directory. I would use a multiple insert without manytomany relation.
This is my entity Document:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Entity
*/
class Document
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank
*/
public $name;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
public $path;
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 up
// when displaying uploaded doc/image in the view.
return 'uploads/documents';
}
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* 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 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->path = $this->getUploadRootDir()."/".$this->getFile()->getClientOriginalName();
// clean up the file property as you won't need it anymore
$this->file = null;
}
}
And this is my controller:
public function uploadAction(Request $request) {
$document = new Document();
$form = $this->createFormBuilder($document)
->add('name')
->add('file',FileType::class,array(
"attr" => array(
"accept" => "image/*",
"multiple" => "multiple",
)
))
->add('save', SubmitType::class, array(
'label' => 'Salva',
'attr' => array('class' => 'btn btn-primary')
))
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$document->upload();
$em->persist($document);
$em->flush();
return $this->redirectToRoute('immovable_upload');
}
return $this->render('AppBundle:Immovable:upload.html.twig', array(
'form' => $form->createView(),
));
}
How to edit my code for insert more image?? It would be enough to have a repeater of the form so that it can only do multiple insertions??
I think you can try embed a collection of forms how described here http://symfony.com/doc/current/cookbook/form/form_collections.html .
And you can also use specific bundle https://github.com/glavweb/GlavwebUploaderBundle for your task.
Related
Because I have a custom built jQuery plugin to pass file uploads to my symfony2 webapp I am looking for ways to handle this upload in the controller.
The standard (non-ajax) file upload that I currently have (and that works fine for synchronous calls) looks like this
Controller excerpt
...
$entity = new Image();
$request = $this->getRequest();
$form = $this->createForm(new ImageType($createAction), $entity);
$form->bind($request); // <-- Find a way to make this connection manually?!
//check that a file was chosen
$fileExists = isset($entity->file);
if ( ($form->isValid()) && ($fileExists) ) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
}
...
Form Type: The form just takes the file and a name:
class ImageType extends AbstractType
{
...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$createAction = $this->createAction;
if ($createAction) {
$builder
->add('file')
;
}
$builder
->add('name', 'text', array('label' => 'Namn'))
;
}
...
}
As I understand (or in other words DON'T apparently understand) the file upload system with symfony2 and doctrine there is quite a bit of magic going on underneath the hood on this call
$form->bind($request);
For example, if I skip this bind() and instead try to create the Image entity manually like this...
$request = $this->getRequest();
$parent = $request->request->get('parent');
$file = $request->request->get('file1');
$name = $request->request->get('name');
$entity->setName( $name );
$entity->setFile( $file );
$entity->setFolder( null );
... I find that it doesn't even have a setFile() so that is taken care of in some other way. Here is that Image entity:
namespace BizTV\MediaManagementBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* BizTV\MediaManagementBundle\Entity\Image
*
* #ORM\Table(name="image")
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Image
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=255)
* #Assert\NotBlank(message = "Bilden måste ha ett namn")
*/
private $name;
/**
* #var integer $width
*
* #ORM\Column(name="width", type="integer")
*/
private $width;
/**
* #var integer $height
*
* #ORM\Column(name="height", type="integer")
*/
private $height;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $path;
//The deleteRequested variable is to flag that an image has been deleted by user.
//Due to slideshow issues we can however not delete the image right away, we can't risk to remove it from the
//cache manifest before the slideshow round is up.
/**
* #var time $deleteRequested
*
* #ORM\Column(name="delete_requested", type="datetime", nullable=true)
*/
private $deleteRequested;
/**
* #var object BizTV\BackendBundle\Entity\company
*
* #ORM\ManyToOne(targetEntity="BizTV\BackendBundle\Entity\company")
* #ORM\JoinColumn(name="company", referencedColumnName="id", nullable=false)
*/
protected $company;
/**
* #var object BizTV\MediaManagementBundle\Entity\Folder
*
* #ORM\ManyToOne(targetEntity="BizTV\MediaManagementBundle\Entity\Folder")
* #ORM\JoinColumn(name="folder", referencedColumnName="id", nullable=true)
*/
protected $folder;
/**
* #Assert\File(maxSize="12000000")
*/
public $file;
/**
* #ORM\OneToOne(targetEntity="BizTV\MediaManagementBundle\Entity\QrImage", mappedBy="image")
*/
protected $qr;
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
// do whatever you want to generate a unique name
$this->path = 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);
}
}
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/images/'.$this->getCompany()->getId();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set width
*
* #param integer $width
*/
public function setWidth($width)
{
$this->width = $width;
}
/**
* Get width
*
* #return integer
*/
public function getWidth()
{
return $this->width;
}
/**
* Set height
*
* #param integer $height
*/
public function setHeight($height)
{
$this->height = $height;
}
/**
* Get height
*
* #return integer
*/
public function getHeight()
{
return $this->height;
}
/**
* Set path
*
* #param string $path
*/
public function setPath($path)
{
$this->path = $path;
}
/**
* Get path
*
* #return string
*/
public function getPath()
{
return $this->path;
}
/**
* Set company
*
* #param BizTV\BackendBundle\Entity\company $company
*/
public function setCompany(\BizTV\BackendBundle\Entity\company $company)
{
$this->company = $company;
}
/**
* Get company
*
* #return BizTV\BackendBundle\Entity\company
*/
public function getCompany()
{
return $this->company;
}
/**
* Set folder
*
* #param BizTV\MediaManagementBundle\Entity\Folder $folder
*/
public function setFolder(\BizTV\MediaManagementBundle\Entity\Folder $folder = NULL)
{
$this->folder = $folder;
}
/**
* Get folder
*
* #return BizTV\MediaManagementBundle\Entity\Folder
*/
public function getFolder()
{
return $this->folder;
}
/**
* Set qr
*
* #param BizTV\MediaManagementBundle\Entity\QrImage $qr
*/
public function setQr(\BizTV\MediaManagementBundle\Entity\QrImage $qr = null)
{
$this->qr = $qr;
}
/**
* Get qr
*
* #return BizTV\MediaManagementBundle\Entity\QrImage
*/
public function getQr()
{
return $this->qr;
}
/**
* Set deleteRequested
*
* #param date $deleteRequested
*/
public function setDeleteRequested($deleteRequested = null)
{
$this->deleteRequested = $deleteRequested;
}
/**
* Get deleteRequested
*
* #return date
*/
public function getDeleteRequested()
{
return $this->deleteRequested;
}
}
I found what I was looking for. To access the files uploaded to symfony from the controller, you just need to do this:
$request = $this->getRequest();
$file = $request->files->get('file1'); //file1 being the name of my form field for the file
/* if your entity is set up like mine - like they teach you in the symfony2 cookbook
* file is actually a public property so you can just set it like this
**/
$entity->file = $file;
//and here's how you get the original name of that file
$entity->setName( $file->getClientOriginalName() );
First of all, if you want to get an entity with your file after form submit/bind/handleRequest or smth else, you need to provide data_class option in form configuration method (setDefaultOptions etc.). And only after that your form will start to return needed entity after submission.
1)First of All create your entity :
namespace XXX;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Table()
* #ORM\Entity
*/
class Article
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $image
* #Assert\File( maxSize = "1024k", mimeTypesMessage = "Please upload a valid Image")
* #ORM\Column(name="image", type="string", length=255)
*/
private $image;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set image
*
* #param string $image
*/
public function setImage($image)
{
$this->image = $image;
}
/**
* Get image
*
* #return string
*/
public function getImage()
{
return $this->image;
}
}
2) build your form: So we will then create a simple form type for this Article entity in order to fit into the other forms: ArticleType.php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class ArticleType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('image')
->add('...')
;
}
public function getName()
{
return 'xxx_articletype';
}
}
3)Create the controller: The controller below shows you how to manage the whole process: ArticleController.php:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
/**
* Article controller.
*
*/
class ArticleController extends Controller
{
/**
* Finds and displays a Article entity.
*
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('XXXBundle:Article')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Article entity.');
}
return $this->render('XXXBundle:Article:show.html.twig', array(
'entity' => $entity,
));
}
/**
* Displays a form to create a new Article entity.
*
*/
public function newAction()
{
$entity = new Article();
//$entity = $em->getRepository('CliniqueGynecoBundle:Article');
$form = $this->createForm(new ArticleType(), $entity);
return $this->render('XXXBundle:Article:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView()
));
}
/**
* Creates a new Article entity.
*
*/
public function createAction()
{
$entity = new Article();
$request = $this->getRequest();
$form = $this->createForm(new ArticleType(), $entity);
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('article_show', array('id' => $entity->getId())));
}
return $this->render('XXXBundle:Article:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView()
));
}
private function createDeleteForm($id)
{
return $this->createFormBuilder(array('id' => $id))
->add('id', 'hidden')
->getForm()
;
}
}
4) layout for the upload form: new.html.twig
<form action="{{ path('basearticle_create') }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<p>
<button class="btn btn-primary" type="submit">Create</button>
</p>
</form>
5) Display layout: show.html.twig
<table>
<tr>
<td align="center" valign="top"><img src="{{ asset('upload/' ~ entity.id ~'/' ~ entity.image)}}" alt="" height="525" width="666" /></td>
</tr>
</table>
6) Use the « Lifecycle Callbacks » hooking the entity in a « Lifecycle callbacks »: « # ORM \ HasLifecycleCallbacks »
/**
*
* #ORM\Table()
* #ORM\HasLifecycleCallbacks
* #ORM\Entity
*/
class Article
{
....
7) Added methods to download files:
class Article
{
....................................
public function getFullImagePath() {
return null === $this->image ? null : $this->getUploadRootDir(). $this->image;
}
protected function getUploadRootDir() {
// the absolute directory path where uploaded documents should be saved
return $this->getTmpUploadRootDir().$this->getId()."/";
}
protected function getTmpUploadRootDir() {
// the absolute directory path where uploaded documents should be saved
return __DIR__ . '/../../../../web/upload/';
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function uploadImage() {
// the file property can be empty if the field is not required
if (null === $this->image) {
return;
}
if(!$this->id){
$this->image->move($this->getTmpUploadRootDir(), $this->image->getClientOriginalName());
}else{
$this->image->move($this->getUploadRootDir(), $this->image->getClientOriginalName());
}
$this->setImage($this->image->getClientOriginalName());
}
/**
* #ORM\PostPersist()
*/
public function moveImage()
{
if (null === $this->image) {
return;
}
if(!is_dir($this->getUploadRootDir())){
mkdir($this->getUploadRootDir());
}
copy($this->getTmpUploadRootDir().$this->image, $this->getFullImagePath());
unlink($this->getTmpUploadRootDir().$this->image);
}
/**
* #ORM\PreRemove()
*/
public function removeImage()
{
unlink($this->getFullImagePath());
rmdir($this->getUploadRootDir());
}
I'm trying to figure out how to do multiple file uploads but without luck. I have not been able to get too much info on this.. Maybe someone here will help me out? :D
Criteria:
I know that in my form type I'm supposed to use multiples for fields; but when I add it, it gives me the this error.
Catchable Fatal Error: Argument 1 passed to
PhotoGalleryBundle\Entity\Image::setFile() must be an instance of
Symfony\Component\HttpFoundation\File\UploadedFile, array given,
called in
/home/action/workspace/www/DecorInterior/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
on line 442 and defined
Image Entity
Here is my code in PHP:
<?php
namespace PhotoGalleryBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* Image
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="PhotoGalleryBundle\Entity\ImageRepository")
* #ORM\HasLifecycleCallbacks
*/
class Image
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="imageCaptation", type="string", length=255)
* #Assert\NotBlank
*/
private $imageCaptation;
/**
* #var string
*
* #ORM\Column(name="imageName", type="string", length=255)
*/
private $imageName;
/**
* #var string
*
* #ORM\Column(name="imageFilePath", type="string", length=255, nullable=true)
*/
private $imageFilePath;
/**
* #var \DateTime
*
* #ORM\Column(name="imageUploadedDate", type="datetime")
* #Gedmo\Timestampable(on="create")
*/
private $imageUploadedDate;
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
/**
* #ORM\ManyToOne(targetEntity="Album", inversedBy="images")
* #ORM\JoinColumn(name="album", referencedColumnName="id")
*/
private $album;
private $temp;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set imageCaptation
*
* #param string $imageCaptation
* #return Image
*/
public function setImageCaptation($imageCaptation)
{
$this->imageCaptation = $imageCaptation;
return $this;
}
/**
* Get imageCaptation
*
* #return string
*/
public function getImageCaptation()
{
return $this->imageCaptation;
}
/**
* Set imageName
*
* #param string $imageName
* #return Image
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
return $this;
}
/**
* Get imageName
*
* #return string
*/
public function getImageName()
{
return $this->imageName;
}
/**
* Set imageFilePath
*
* #param string $imageFilePath
* #return Image
*/
public function setImageFilePath($imageFilePath)
{
$this->imageFilePath = $imageFilePath;
return $this;
}
/**
* Get imageFilePath
*
* #return string
*/
public function getImageFilePath()
{
return $this->imageFilePath;
}
/**
* Get imageUploadedDate
*
* #return \DateTime
*/
public function getImageUploadedDate()
{
return $this->imageUploadedDate;
}
/**
* Set file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null) {
$this->file = $file;
// check if we have an old image path
if (isset($this->imageFilePath)) {
// store the old name to delete after the update
$this->temp = $this->imageFilePath;
$this->imageFilePath = null;
} else {
$this->imageFilePath = 'initial';
}
}
/**
* #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->imageFilePath = $fileName . '.' . $this->getFile()->guessExtension();
}
}
/**
* #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
$this->getFile()->move($this->getUploadRootDir(), $this->imageFilePath);
// 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->imageFilePath = null;
}
/**
* #ORM\PostRemove()
*/
public function removeUpload() {
$file = $this->getAbsolutePath();
if ($file) {
unlink($file);
}
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile() {
return $this->file;
}
public function getAbsolutePath() {
return null === $this->imageFilePath
? null
: $this->getUploadRootDir() . '/' . $this->imageFilePath;
}
public function getWebPath() {
return null === $this->imageFilePath
? null
: $this->getUploadDir() . '/' . $this->imageFilePath;
}
public function getUploadRootDir() {
// the absolute path where uploaded
// 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/image in the view
return 'uploads/images';
}
/**
* Set album
*
* #param \PhotoGalleryBundle\Entity\Album $album
* #return Image
*/
public function setAlbum(\PhotoGalleryBundle\Entity\Album $album = null)
{
$this->album = $album;
return $this;
}
/**
* Get album
*
* #return \PhotoGalleryBundle\Entity\Album
*/
public function getAlbum()
{
return $this->album;
}
}
ImageType:
<?php
namespace PhotoGalleryBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ImageType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('imageCaptation')
->add('imageName')
->add('file', 'file', array('multiple' => TRUE))
->add('album')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'PhotoGalleryBundle\Entity\Image'
));
}
/**
* #return string
*/
public function getName()
{
return 'photogallerybundle_image';
}
}
In this case the value will be an array of UploadedFile objects. Given that you have a type with multiple files yet only one caption, name, etc I assume you will want to refactor Image to support multiple image files. In this case the file property would be better known as files and setFiles should accept an array.
If however you want have one Image entity per file uploaded, consider implementing a collection instead.
Alternatively you could manually process the form in your action. For example:
public function uploadAction(Request $request)
{
foreach ($request->files as $uploadedFile) {
$uploadedFile = current($uploadedFile['file']);
// Build Image using each uploaded file
}
...
Here is my form type:
class TestFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('thumbnail', 'hidden', array(
'label' => 'Thumbnail',
'label_attr' => array(
'class' => 'col-xs-2 control-label'
),
'required' => false,
'error_bubbling' => true,
'required' => false
));
$builder->add('thumbnail_data', 'file', array(
'error_bubbling' => true,
'required' => false
));
}
public function setDefaultOptions(\Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'X\LibraryBundle\Entity\Test',
'cascade_validation' => true,
'error_bubbling' => true,
));
}
public function getName()
{
return 'test';
}
}
Here is the entity, important part is the setThumbnailData($file) method, which stores the thumbnail file and sets the thumbnail path via the setThumbnail(string) method.
<?php
namespace X\LibraryBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Test
*
* #ORM\Table(name="test")
* #ORM\Entity(repositoryClass="X\LibraryBundle\Repository\TestRepository")
*/
class Test
{
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="text", nullable=true)
*/
protected $thumbnail;
/**
* Set thumbnail
*
* #param string $thumbnail
* #return Test
*/
public function setThumbnail($thumbnail)
{
$this->thumbnail = $thumbnail;
return $this;
}
/**
* Get thumbnail
*
* #return string
*/
public function getThumbnail()
{
return $this->thumbnail;
}
/**
* This will save file to disk
* #param $file
*/
public function setThumbnailData($file) {
if($file !== null && $file !== false)
{
$fileName = $file->getClientOriginalName();
$baseDir = __DIR__ . '/../../../../../../../web/uploads/promotional_material/';
$dir = sha1(microtime());
while (is_dir($baseDir . $dir)) {
$dir = sha1(microtime());
}
mkdir($baseDir . $dir);
$this->setThumbnail('/uploads/promotional_material/' . $dir . '/' . $fileName);
$file->move($baseDir . $dir, $fileName);
}
}
public function getThumbnailData() {
return '';
}
}
Now the issue is, if the form runs into a validation error, the following twig lines produce different output, the correct value outputted from the second line, the other produces the original thumbnail path. So if I output the thumbnail input using {{ form_widget(form.thumbnail) }}, I get the original thumbnail path, not the one that should have changed via the setThumbnailData() method.
{{ dump(form.thumbnail.vars.data) }}
{{ dump(form.vars.data.thumbnail) }}
Is the issue caused by setting the thumbnail using the setThumbnailData() method? Not sure how to fix this other than hard coding the input in twig like so:
<input type="hidden" name="test[thumbnail]" value="{{ form.vars.value.thumbnail }}"/>
I cannot exactly solve your problem, because in my opinion you're doing it the wrong way. A data class is actually only responsibly for keeping your data, so your method set/getThumbnailData should look like this
<?php
// ...
private $thumbnailData;
public function setThumbnailData(UploadedFile $thumbnailData) {
$this->thumbnailData = $thumbnailData;
}
public function getThumbnailData() {
return $this->thumbnailData;
}
In your controller you have something like this:
<?php
// ...
public function formAction() {
$form = // ...
$form->handleRequest($request);
if($form->isValid()) {
$test = $form->getData();
/* #var $test Test */
$thumbnailUploader = $this->get('thumbnail_uploader');
/* #var $thumbnailUploader ThumbnailUploadService */
$file = $test->getThumbnailData();
$filename = $file->getClientOriginalName();
$thumbnailUploader->upload($filename, $file);
// ...
}
}
I recommend to move all logic which has nothing to do with forms or requests into services. Your service in this case can be more generic handle any symfony files with a given filename.
<?php
class ThumbnailUploadService {
/**
* #param $file Symfony\Component\HttpFoundation\File\File
*
*/
public function upload($filename, File $file) {
// ...
}
}
Also there is another way using callbacks. Here i give you a generic class to uploads image and generate the thumbnails. I hope this work for you. This class manage the files generated automatically when you create update o delete the entity.
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints as DoctrineAssert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
use App\MyBundleBundle\Util\Util;
/**
* Image
*
* #ORM\Table(name="image")
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
*/
class Image
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string")
* #Assert\NotBlank()
*/
private $name;
private $temp;
/**
* #ORM\Column(type="string", length=255)
*/
protected $path;
/**
* #Assert\Image(maxSize="5M")
*/
private $file;
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file= $file;
if (isset($this->path)) {
$this->temp = $this->path;
$this->path= null;
} else {
$this->path= 'initial';
}
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->getFile()) {
// do whatever you want to generate a unique name
$filename = Util::getSlug($this->name) . uniqid() . '.' . $this->file->guessExtension();
$this->path = $filename;
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->getFile()) {
return;
}
$this->getFile()->move($this->getUploadRootDir(), $this->path);
if (isset($this->temp)) {
if (file_exists($this->getUploadRootDir() .'/'. $this->temp)) {
unlink($this->getUploadRootDir() . '/' . $this->temp);
}
$this->temp = null;
}
$this->file= null;
//create a dir to save de thumbnails
if (!file_exists($this->getUploadRootDir() . '/thumbnails')) {
mkdir($this->getUploadRootDir() . '/thumbnails');
}
//call a method in util class to generate the thumbnails
Util::redim($this->getUploadRootDir() . '/' . $this->path, $this->getUploadRootDir() . '/thumbnails/' . $this->path, 128, 128);
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
//This is to remove the files when the entity is delete
if ($file = $this->getAbsolutePath()) {
if (file_exists($file)) {
unlink($file);
$thumb = $this->getUploadRootDir() . '/thumbnails/' . $this->getPath();
if (file_exists($thumb)) {
unlink($thumb);
}
}
}
}
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()
{
return $this->getUploadDir();
}
protected function getUploadDir()
{
return 'uploads/image/';
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set nombre
*
* #param string $name
* #return Image
*/
public function setName($name)
{
$this->name= $name;
return $this;
}
/**
* Get nombre
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set path
*
* #param string $path
* #return Imagen
*/
public function setPath($path)
{
$this->path= $path;
return $this;
}
/**
* Get path
*
* #return string
*/
public function getPath()
{
return $this->path;
}
public function __toString()
{
return $this->getName();
}
}
I tried to upgrade my existing and working single file upload form to a multi file upload with add and remove function. Therefor I used this docu: http://symfony.com/doc/current/cookbook/form/form_collections.html#allowing-new-tags-with-the-prototype
My Entity is not called tags but attachments.
I can add via Javascript unlimited name + file fields. After adding, I get this exception:
FatalErrorException: Error: Call to a member function contains() on a non-object in /Applications/MAMP/htdocs/Seotool/src/Seotool/MainBundle/Entity/Attachments.php line 232
I also think, that not all in my code is correct while I tried to upgrade to multiple upload function. Maybe someone can help me get all fixed?
That's my current code, starting with my controller, who is generating the form.
/**
#Route(
* path = "/taskmanager/user/{user_id}",
* name = "taskmanager"
* )
* #Template()
*/
public function taskManagerAction($user_id, Request $request)
{
/* #### NEW TASK #### */
$task = new Task();
$attachment = new Attachments();
$task->getAttachments()->add($attachment);
$addTaskForm = $this->createForm(new TaskType(), $task);
$addTaskForm->handleRequest($request);
if($addTaskForm->isValid()):
/* User Object of current Users task list */
$userid = $this->getDoctrine()
->getRepository('SeotoolMainBundle:User')
->find($user_id);
$task->setDone(FALSE);
$task->setUser($userid);
$task->setDateCreated(new \DateTime());
$task->setDateDone(NULL);
$task->setTaskDeleted(FALSE);
$attachment->setTask($task);
$attachment->setUser($userid);
$attachment->upload();
$em = $this->getDoctrine()->getManager();
$em->persist($task);
$em->flush();
$this->log($user_id, $task->getId(), 'addTask');
return $this->redirect($this->generateUrl('taskmanager', array('user_id' => $user_id)));
endif;
.....
This are particular code snippets of my Task.php Entity
<?php
namespace Seotool\MainBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="tasks")
*/
class Task {
....
/**
* #ORM\OneToMany(targetEntity="Attachments", mappedBy="task", cascade={"persist"})
*/
protected $attachments;
....
/**
* Constructor
*/
public function __construct()
{
$this->log = new \Doctrine\Common\Collections\ArrayCollection();
$this->attachments = new \Doctrine\Common\Collections\ArrayCollection();
}
....
/**
* Add attachments
*
* #param \Seotool\MainBundle\Entity\Attachments $attachments
* #return Task
*/
public function addAttachment(\Seotool\MainBundle\Entity\Attachments $attachments)
{
$attachments->addTask($this);
$this->attachments->add($attachments);
}
/**
* Remove attachments
*
* #param \Seotool\MainBundle\Entity\Attachments $attachments
*/
public function removeAttachment(\Seotool\MainBundle\Entity\Attachments $attachments)
{
$this->attachments->removeElement($attachments);
}
/**
* Get attachments
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getAttachments()
{
return $this->attachments;
}
This is my Attachments.php Entity
<?php
namespace Seotool\MainBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="attachments")
*/
class Attachments {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank
*/
public $name;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
public $path;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="attachments")
* #ORM\JoinColumn(name="user", referencedColumnName="id")
*/
protected $User;
/**
* #ORM\ManyToOne(targetEntity="Task", inversedBy="attachments")
* #ORM\JoinColumn(name="task", referencedColumnName="id")
*/
protected $task;
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->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 up
// when displaying uploaded doc/image in the view.
return 'uploads';
}
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->path = $this->getFile()->getClientOriginalName();
// clean up the file property as you won't need it anymore
$this->file = null;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Attachments
*/
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 Attachments
*/
public function setPath($path)
{
$this->path = $path;
return $this;
}
/**
* Get path
*
* #return string
*/
public function getPath()
{
return $this->path;
}
/**
* Set User
*
* #param \Seotool\MainBundle\Entity\User $user
* #return Attachments
*/
public function setUser(\Seotool\MainBundle\Entity\User $user = null)
{
$this->User = $user;
return $this;
}
/**
* Get User
*
* #return \Seotool\MainBundle\Entity\User
*/
public function getUser()
{
return $this->User;
}
/**
* Set Task
*
* #param \Seotool\MainBundle\Entity\Task $task
* #return Attachments
*/
public function setTask(\Seotool\MainBundle\Entity\Task $task = null)
{
$this->task = $task;
return $this;
}
/**
* Get Task
*
* #return \Seotool\MainBundle\Entity\Task
*/
public function getTask()
{
return $this->task;
}
public function addTask(Task $task)
{
if (!$this->task->contains($task)) {
$this->task->add($task);
}
}
}
This one is my TaskType.php Form Type Class:
<?php
namespace Seotool\MainBundle\Form\Type;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
....
$builder->add('attachments', 'collection', array(
'type' => new AttachmentsType(),
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'by_reference' => false,
));
This is my AttachmentsType.php Form Type Class:
<?php
namespace Seotool\MainBundle\Form\Type;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class AttachmentsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text', array(
'label' => 'Dateiname',
'required' => false,
));
$builder->add('file', 'file', array(
'label' => false,
'required' => false,
"attr" => array(
"multiple" => "multiple",
)
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver
->setDefaults(array(
'data_class' => 'Seotool\MainBundle\Entity\Attachments'
));
}
public function getName()
{
return 'attachments';
}
}
Your problem is that you are using the wrong call when adding your attachment.
Your association between your attachment and task is a manyToOne meaning that attachments needed to be added to a task but a task needs to be set on an attachment.
To sort out your problem your should just change your add attachment call from
public function addAttachment(\Seotool\MainBundle\Entity\Attachments $attachments)
{
$attachments->addTask($this);
// this should be set not add
$this->attachments->add($attachments);
}
to
public function addAttachment(\Seotool\MainBundle\Entity\Attachments $attachment)
{
$attachment->setTask($this);
$this->attachments->add($attachment);
}
I have a Document Entity and I want that the user of the website be able to download the files that have been uploaded.
I don't know how to do this (I tried with a downloadAction in my DocumentController but I have some errors).
Here is my Document entity :
<?php
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints as DoctrineAssert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* MyBundle\Entity\Document
* #ORM\Table()
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
*/
class Document
{
public function __toString() {
return $this->document_type->getName();
}
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var date $endDate
*
* #ORM\Column(name="endDate", type="date")
*/
private $endDate;
/**
* #ORM\ManyToOne(targetEntity="DocumentType")
* #ORM\JoinColumn(name="document_type_id", referencedColumnName="id")
*/
private $document_type;
/**
* #ORM\Column(type="string", length="255", nullable="TRUE")
* #Assert\File(maxSize="6000000")
*/
public $file;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
public $path;
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) {
// do whatever you want to generate a unique name
$this->path = uniqid().'.'.$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 endDate
*
* #param date $endDate
*/
public function setEndDate($endDate)
{
$this->endDate = $endDate;
}
/**
* Get endDate
*
* #return endDate
*/
public function getEndDate()
{
return $this->endDate;
}
/**
* Set document_type
*
* #param Was\RHBundle\Entity\DocumentType $documentType
*/
public function setDocumentType(MyBundle\Entity\DocumentType $documentType)
{
$this->document_type = $documentType;
}
/**
* Get document_type
*
* #return MyBundle\Entity\DocumentType
*/
public function getDocumentType()
{
return $this->document_type;
}
/**
* 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;
}
}
Now, here is my downloadAction in my DocumentController.php :
public function downloadAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$document = $em->getRepository('MyBundle:Document')->find($id);
if (!$document) {
throw $this->createNotFoundException('Unable to find the document');
}
$headers = array(
'Content-Type' => $document->getMimeType()
'Content-Disposition' => 'attachment; filename="'.$document->getDocumentType().'"'
);
$filename = $document->getUploadRootDir().'/'.$document->getDocumentType();
return new Response(file_get_contents($filename), 200, $headers);
}
I've got this error :
Call to undefined method MyBundle\Entity\Document::getMimeType()
I use IgorwFileServeBundle.
Here a sample I used on one of my project :
$em = $this->getDoctrine()->getEntityManager();
$file = $em->getRepository('MyBundle:File')->find($id);
$path = $file->getPath();
$mimeType = $file->getMimeType();
$folder = 'Public';
$factory = $this->get('igorw_file_serve.response_factory');
$response = $factory->create($folder.'/'.$path, $mimeType);
return $response;
I hope it can help
Here is the code from one of my project
public function downloadAction()
{
$items = $this->get('xxxx')->getXXX();
$response = $this->render('xxx:xxx:xxx.csv.twig', array('items' => $items));
$response->headers->set('Content-Type', 'text/csv');
$response->headers->set('Content-Disposition', 'attachment; filename=budget.csv');
return $response;
}
Add variable $mimeType to the Document entity
/**
* #ORM\Column()
* #Assert\NotBlank
*/
private $mimeType;
and getters and setters (or generate them)
public function setMimeType($mimeType) {
$this->mimeType = $mimeType;
return $this;
}
public function getMimeType() {
return $this->mimeType;
}
update the database schema
php app/console doctrine:schema:update --force
and add setMimeType to your setFile function
/**
* Set file
*
* #param string $file
*/
public function setFile($file)
{
$this->file = $file;
$this->setMimeType($this->getFile()->getMimeType());
}
Then your controller will work properly.