I am trying to upload file in my Symfony Project, using this tutorial:
http://symfony.com/doc/current/controller/upload_file.html
Symfony shows no error, but when I upload the file nothing happens, and in my DB instead of having the filename.jpg I have this line C:\wamp64\tmp\php9294.tmp
Here is my Entity :
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
/**
* #ORM\Entity(repositoryClass="App\Repository\ItemRepository")
*/
class Item
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string")
* #Assert\NotBlank(message="Upload your image")
* #Assert\File(mimeTypes={ "image/png", "image/jpeg" })
*/
private $image;
public function getImage()
{
return $this->image;
}
public function setImage($image)
{
$this->image = $image;
return $this;
}
My form :
namespace App\Form;
use App\Entity\Item;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class ItemType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('image',FileType::class,array('data_class'=> null, 'label' => 'Image'))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Item::class,
));
}
}
My Controller :
namespace App\Controller;
use App\Entity\Item;
use App\Form\ItemType;
use App\Repository\ItemRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/item")
*/
class ItemController extends Controller
{
/**
* #Route("/", name="item_index", methods="GET")
*/
/**
* #Route("/new", name="item_new", methods="GET|POST")
*/
public function new(Request $request): Response
{
$item = new Item();
$form = $this->createForm(ItemType::class, $item);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$file = $item->getImage();
$fileName = $this->generateUniqueFileName().'.'.$file->guessExtension();
// moves the file to the directory where brochures are stored
$file->move(
$this->getParameter('brochures_directory'),
$fileName
);
$item->setImage($fileName);
return $this->redirectToRoute('item_index');
}
return $this->render('item/new.html.twig', [
'item' => $item,
'form' => $form->createView(),
]);
}
/**
* #return string
*/
private function generateUniqueFileName()
{
// md5() reduces the similarity of the file names generated by
// uniqid(), which is based on timestamps
return md5(uniqid());
}
}
I followed exactly the steps said in the tutorial but the image doesn't seems to be uploaded in my folder
I had the same problem, and I found out that it was a matter of permissions, if someone else got stuck in the part, give the folder all permissions and try it again.
Hope it could be helpful.
I recommand you to use the StofDoctrineExtensionsBundle: https://symfony.com/doc/master/bundles/StofDoctrineExtensionsBundle/index.html
The uploadable extensions is for you, is help you to upload file with some services.
In first, require it: composer require stof/doctrine-extensions-bundle
Now, configure it in the file config/packages/stof_doctrine_extensions.yml like that (considering the folder public/uploads is created with read/write permission):
stof_doctrine_extensions:
default_locale: en_US
uploadable:
default_file_path: "%kernel.root_dir%/../public/uploads"
mime_type_guesser_class: Stof\DoctrineExtensionsBundle\Uploadable\MimeTypeGuesserAdapter
default_file_info_class: Stof\DoctrineExtensionsBundle\Uploadable\UploadedFileInfo
orm:
default:
uploadable: true
Now, create the Entity/File.php for use it:
<?php
namespace App\Entity;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as Serializer;
use Overblog\GraphQLBundle\Annotation\GraphQLColumn;
/**
* #ORM\Entity
* #Gedmo\Uploadable(filenameGenerator="SHA1", allowOverwrite=true, appendNumber=true)
*/
class File
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(name="path", type="string")
* #Gedmo\UploadableFilePath
*/
protected $path;
/**
* #ORM\Column(name="name", type="string")
* #Gedmo\UploadableFileName
*/
protected $name;
/**
* #ORM\Column(name="mime_type", type="string")
* #Gedmo\UploadableFileMimeType
*/
protected $mimeType;
/**
* #ORM\Column(name="size", type="decimal")
* #Gedmo\UploadableFileSize
*/
protected $size;
/**
* #GraphQLColumn(type="String")
*/
protected $publicPath;
/**
* #return string
*/
public function getPublicPath()
{
return '/uploads/'.$this->name;
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #return mixed
*/
public function getPath()
{
return $this->path;
}
/**
* #param mixed $path
*
* #return File
*/
public function setPath($path)
{
$this->path = $path;
return $this;
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*
* #return File
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* #return mixed
*/
public function getMimeType()
{
return $this->mimeType;
}
/**
* #param mixed $mimeType
*
* #return File
*/
public function setMimeType($mimeType)
{
$this->mimeType = $mimeType;
return $this;
}
/**
* #return mixed
*/
public function getSize()
{
return $this->size;
}
/**
* #param mixed $size
*
* #return File
*/
public function setSize($size)
{
$this->size = $size;
return $this;
}
}
The last step is to create a relation the File entity. After that, you can concretize the upload using the UploadManager like that:
<?php
public function uploadFile(\Stof\DoctrineExtensionsBundle\Uploadable\UploadableManager $uploadableManager)
{
$uploadableManager->markEntityToUpload($file, $fileInfo);
}
And just persist/flush it.
Related
I made an application with Symfony, and I am using a Media Entity to allow uploading files and linking them with other entities (ex: an image with an article).
If I want to create a new article with a picture everything will work perfectly, but if later I want to change this same picture it wouldn't work. Furthermore if at the same time I update another field, such as title or anything else, I will see them all change except the picture.
Not a single error is returned so I guess this has to be linked with the configuration, but I couldn't find any issues.
My code:
Media.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Media
*
* #ORM\Table("media")
* #ORM\Entity(repositoryClass="AppBundle\Repository\MediaRepository")
* #ORM\HasLifecycleCallbacks
*/
class Media
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\COlumn(name="updated_at",type="datetime", nullable=true)
*/
private $updateAt;
/**
* #ORM\PostLoad()
*/
public function postLoad()
{
$this->updateAt = new \DateTime();
}
/**
* #ORM\Column(type="string",length=255, nullable=true)
*/
public $path;
public $file;
public function getUploadRootDir()
{
return realpath(__dir__.'/../../../web/img/upload');
}
public function getAbsolutePath()
{
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->path;
}
public function getAssetPath()
{
return 'img/upload/'.$this->path;
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
$this->tempFile = $this->getAbsolutePath();
$this->oldFile = $this->getPath();
$this->updateAt = new \DateTime();
if (null !== $this->file) {
$this->path = sha1(uniqid(mt_rand(), true)).'.'.$this->file->guessExtension();
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
$extentions_allowed = array('jpeg', 'png', 'svg', 'gif');
if (null !== $this->file) {
if(in_array($this->file->guessExtension(), $extentions_allowed))
{
$this->file->move($this->getUploadRootDir(), $this->path);
unset($this->file);
if ($this->oldFile != null) unlink($this->tempFile);
}else {
unset($this->file);
throw new \Exception('Seules les images sont autorisées (jpg/png/gif/svg)');
}
}
}
/**
* #ORM\PreRemove()
*/
public function preRemoveUpload()
{
$this->tempFile = $this->getAbsolutePath();
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
if(file_exists($this->tempFile)){
unlink($this->tempFile);
}
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function getPath()
{
return $this->path;
}
}
Controller which updates the entity (Article for instance)
public function blocAction(Request $request, Bloc $bloc = null){
if($bloc){
$form = $this->createForm('AppBundle\Form\BlocType', $bloc, array(
'type' => $bloc->getType()
));
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid() ){
try{
$this->getDoctrine()->getManager()->persist($bloc);
$this->getDoctrine()->getManager()->flush();
}catch (\Exception $e){
$this->addFlash(
'error',
$e->getMessage()
);
}
return $this->redirectToRoute('app_admin_bloc', array('id' => $bloc->getId()));
}
return $this->render('admin/bloc.html.twig', array(
'bloc' => $bloc,
'form' => $form->createView()
));
}else{
return $this->redirectToRoute('app_admin_index');
}
}
The form of the entity
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MediaType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file',FileType::class, array(
'attr' => array(
'class' => 'image-preview'
)
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Media'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'app_bundle_media';
}
}
First of all you should clean your Media entity because it mixes public with private properties and it has too much logic. A best practice would be:
to set all your properties to private and access them via getter/setter methods;
put your upload logic ideally in a service that does only that.
Then in order for your form to trigger errors you need to add assertions annotations to your entity:
example:
use Symfony\Component\Validator\Constraints as Assert;
class Media
{
//...
/**
* #ORM\Column(type="string",length=255, nullable=true)
*/
private $path;
/**
* #Assert\NotBlank()
* #Assert\File(
* mimeTypes={"image/jpeg", "image/png", "application/svg+xml", "image/gif"},
* message="Seules les images sont autorisées (jpg/png/gif/svg)"
* )
*/
private $file;
//...
That way you will get rid of your current $extentions_allowed check.
Have a look at this detailed doc on how to upload a file properly: https://symfony.com/doc/current/controller/upload_file.html
I don't find solutions for several days to resolve my issue. I want to upload multiple images when I create a Post. My Post entity has a OneToMany relation with Image Entity. I use an embedded form in my PostType. It is a CollectionType of ImageType::class. To manage my upload functionality, I wrote an event listener called ImageUploadListener which injects my custom service called FileUploader.
This listener call a uploadFile function when a preUpdate and a prePersist event is handled. This function call my upload function from my FileUploader to move the image/file... to a target directory and to return a filename.
After that, I try to instantiate an Image Entity and to set the appropriate datas. But it doesn't work, only a file seems to be stored properly but with an additional entry unwanted in my db. (In my Image Table, for one image uploaded, I've got an entry with id 1 for example, a post_id set to NULL and file field set to /tmp/random number).
Please, could you help me ?
Post Entity
namespace UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Post
*
* #ORM\Table(name="post")
* #ORM\Entity(repositoryClass="UserBundle\Repository\PostRepository")
*/
class Post
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="Title", type="string", length=255)
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="Content", type="text")
*/
private $content;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="posts")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/**
* #ORM\OneToMany(targetEntity="Image", mappedBy="post", cascade={"persist", "remove"})
*/
private $images;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
*
* #return Post
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set content
*
* #param string $content
*
* #return Post
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* #return string
*/
public function getContent()
{
return $this->content;
}
/**
* Set post
*
* #param \
*
* #return Post
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
/**
* Get post
*
* #return \?
*/
public function getUser()
{
return $this->user;
}
/**
* Constructor
*/
public function __construct()
{
$this->images = new ArrayCollection();
}
/**
* Add image
*
* #param \UserBundle\Entity\Image $image
*
* #return Post
*/
public function addImage(\UserBundle\Entity\Image $image)
{
$this->images[] = $image;
return $this;
}
/**
* Remove image
*
* #param \UserBundle\Entity\Image $image
*/
public function removeImage(\UserBundle\Entity\Image $image)
{
$this->images->removeElement($image);
}
/**
* Get images
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getImages()
{
return $this->images;
}
}
Image Entity
namespace UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="UserBundle\Entity\ImageRepository")
*/
class Image
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", nullable=true)
*/
private $file;
public function setFile($file)
{
$this->file = $file;
return $this;
}
public function getFile()
{
return $this->file;
}
/**
* #ORM\ManyToOne(targetEntity="Post", inversedBy="images")
* #ORM\JoinColumn(name="post_id", referencedColumnName="id")
*/
private $post;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set post
*
* #param \UserBundle\Entity\Post $post
*
* #return Image
*/
public function setPost(\UserBundle\Entity\Post $post = null)
{
$this->post = $post;
return $this;
}
/**
* Get post
*
* #return \UserBundle\Entity\Post
*/
public function getPost()
{
return $this->post;
}
}
PostType
namespace UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
//use Ivory\CKEditorBundle\Form\Type\CKEditorType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use UserBundle\Entity\Post;
class PostType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('content')
->add('images', CollectionType::class, array (
'entry_type' => ImageType::class,
'entry_options' => array('label' => false),
'allow_add' => true,
'allow_delete' => true
))
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
//'data_class' => 'UserBundle\Entity\Post',
'data_class' => Post::class,
//'csrf_protection' => false
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'userbundle_post';
}
}
ImageType
namespace UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use UserBundle\Entity\Image;
class ImageType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file', FileType::class, [
'required' => false,
'data_class' => null,
])
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Image::class,
//'csrf_protection' => false
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'userbundle_image';
}
}
EDIT: ImageUploadListener
//src/UserBundle/EventListener/ImageUploadListener.php
namespace UserBundle\EventListener;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\File\File;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use UserBundle\Entity\User;
use UserBundle\Entity\Post;
use UserBundle\Entity\Image;
use UserBundle\Service\FileUploader;
class ImageUploadListener
{
private $uploader;
public function __construct(FileUploader $uploader)
{
$this->uploader = $uploader;
}
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$this->uploadFile($entity);
}
public function preUpdate(PreUpdateEventArgs $args)
{
$entity = $args->getEntity();
$this->uploadFile($entity);
}
private function uploadFile($entity)
{
if ($entity instanceof Post) {
$post = $entity;
$images = $post->getImages();
foreach ($images as $image) {
if ($image->getFile() instanceof UploadedFile) {
$imageName = $this->uploader->upload($image->getFile());
// to avoid persisting FileObject in DB
$post->removeImage($image);
$postImage = new Image();
$postImage->setFile($imageName);
$postImage->setPost($post);
$post->addImage($postImage);
}
}
}
return;
}
}
FileUploader
namespace UserBundle\Service;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class FileUploader
{
private $targetDir;
public function __construct($targetDir)
{
$this->targetDir = $targetDir;
}
public function upload(UploadedFile $file)
{
$fileName = md5(uniqid()).'.'.$file->guessExtension();
$file->move($this->getTargetDir(), $fileName);
return $fileName;
}
public function getTargetDir()
{
return $this->targetDir;
}
}
I got entity called SupportMessage which contains messages from my support (ticket) system. I want to implement feature which allows users and support agents attach files to their posts.
I also got an entity called Files where all files from my project are listed: file ID, file name, user and uploading date.
When user writes a message in my support system, he can attach multiple files. I think using multiple=true is more elegant way than creating CollectionType of FileType buttons, but I don't really know how to implement this feature and make it works. Didn't find any information in official docs and Google about this case.
When I send the form, I got an array of UploadedFile object, but not ArrayCollection, so everything fails:
Expected value of type "Doctrine\Common\Collections\Collection|array" for association field "AppBundle\Entity\SupportMessage#$attachments", got "Symfony\Component\HttpFoundation\File\UploadedFile" instead.
Controller:
/**
* #Security("is_granted('ALLOWED_TO_VIEW_SUPPORT_TICKET', supportTicket)")
* #Route("/support/ticket-{supportTicket}", name="view_ticket")
*
* #param Request $request
* #param SupportTicket $supportTicket
* #return Response
*/
public function viewTicket(Request $request, SupportTicket $supportTicket)
{
$translator = $this->get('translator');
$breadcrumbs = $this->get('white_october_breadcrumbs');
$breadcrumbs->addRouteItem('app.name', 'homepage');
$breadcrumbs->addRouteItem('page_title.support', 'my_support_tickets');
$breadcrumbs->addItem($supportTicket->getTitle());
$supportMessage = new SupportMessage();
$supportMessage->setSupportTicket($supportTicket);
$supportMessage->setUser($this->getUser());
$form = $this->createForm(SupportMessageType::class, $supportMessage);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
foreach ($supportMessage->getAttachments() as $attachment) {
$fileName = $this->get('app.file_uploader')->upload($attachment);
$file = new File();
$file->setFilename($fileName);
$file->setUser($this->getUser());
//$supportMessage->addAttachment($file);
}
//dump($supportMessage);die;
$em = $this->getDoctrine()->getManager();
$em->persist($supportMessage);
$em->flush();
$this->addFlash('notice', $translator->trans('support.flash_message.sent'));
return $this->redirect($request->getUri());
}
return $this->render('support/view-ticket.html.twig', [
'title' => $supportTicket->getTitle(),
'supportTicket' => $supportTicket,
'form' => $form->createView()
]);
}
Service:
namespace AppBundle\Service;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class FileUploader
{
private $targetDir;
public function __construct($targetDir)
{
$this->targetDir = $targetDir;
}
public function upload(UploadedFile $file)
{
$fileName = md5(uniqid()).'.'.$file->guessExtension();
$file->move($this->targetDir, $fileName);
return $fileName;
}
}
SupportMessage entity:
namespace AppBundle\Entity;
use Carbon\Carbon;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
*/
class SupportMessage
{
public function __construct()
{
$this->postedAt = new \DateTime();
$this->attachments = new ArrayCollection();
}
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*
* #var int
*/
private $id;
/**
* #ORM\Column(type="text")
*
* #Assert\NotBlank
* #Assert\Length(max=65535)
*
* #var string
*/
private $message;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
private $postedAt;
/**
* #return int
*/
public function getId(): ?int
{
return $this->id;
}
/**
* #return string
*/
public function getMessage(): ?string
{
return $this->message;
}
/**
* #param string $message
*/
public function setMessage(?string $message)
{
$this->message = $message;
}
/**
* #return \DateTime
*/
public function getPostedAt()
{
return $this->postedAt;
}
/**
* #return string
*/
public function getPostedAgo()
{
Carbon::setLocale('ru');
return Carbon::instance($this->postedAt)->diffForHumans();
}
/**
* #param \DateTime $postedAt
*/
public function setPostedAt($postedAt)
{
$this->postedAt = $postedAt;
}
/**
* #ORM\ManyToOne(targetEntity="User")
* #ORM\JoinColumn(nullable=false)
*
* #var User
*/
private $user;
/**
* #return User
*/
public function getUser()
{
return $this->user;
}
/**
* #param User $user
*/
public function setUser($user)
{
$this->user = $user;
}
/**
* #ORM\ManyToOne(targetEntity="SupportTicket", inversedBy="supportMessages")
*
* #var SupportTicket
*/
private $supportTicket;
/**
* #return SupportTicket
*/
public function getSupportTicket()
{
return $this->supportTicket;
}
/**
* #param SupportTicket $supportTicket
*/
public function setSupportTicket($supportTicket)
{
$this->supportTicket = $supportTicket;
}
/**
* #ORM\ManyToMany(targetEntity="File", inversedBy="supportMessages")
*
* #var File[]
*/
private $attachments;
/**
* #return File[]
*/
public function getAttachments()
{
return $this->attachments;
}
/**
* #param File[] $attachments
*/
public function setAttachments($attachments)
{
foreach ($attachments as $attachment) {
$this->attachments->add($attachment);
}
//dump($this->attachments);die;
}
/**
* #param File $attachment
*/
public function addAttachment($attachment)
{
$this->attachments->add($attachment);
}
}
File entity:
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
*/
class File
{
public function __construct()
{
$this->uploadedAt = new \DateTime();
$this->supportMessages = new ArrayCollection();
}
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*
* #var int
*/
private $id;
/**
* #ORM\Column(type="string")
*
* #Assert\File
*
* #var string
*/
private $filename;
/**
* #ORM\Column(type="string")
*
* #var User
*/
private $user;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
private $uploadedAt;
/**
* #return int
*/
public function getId(): ?int
{
return $this->id;
}
/**
* #return string
*/
public function getFilename(): ?string
{
return $this->filename;
}
/**
* #param string $filename
*/
public function setFilename(?string $filename)
{
$this->filename = $filename;
}
/**
* #return User
*/
public function getUser()
{
return $this->user;
}
/**
* #param mixed $user
*/
public function setUser($user)
{
$this->user = $user;
}
/**
* #return \DateTime
*/
public function getUploadedAt()
{
return $this->uploadedAt;
}
/**
* #param \DateTime $uploadedAt
*/
public function setUploadedAt($uploadedAt)
{
$this->uploadedAt = $uploadedAt;
}
/**
* #ORM\ManyToMany(targetEntity="SupportMessage", mappedBy="attachments")
*
* #var Collection|SupportMessage[]
*/
private $supportMessages;
/**
* #return Collection|SupportMessage[]
*/
public function getSupportMessages()
{
return $this->supportMessages;
}
/**
* #param Collection|SupportMessage[] $supportMessages
*/
public function setSupportMessages($supportMessages)
{
$this->supportMessages = $supportMessages;
}
/**
* #param SupportMessage $supportMessage
*/
public function addSupportMessage($supportMessage)
{
$supportMessage->addAttachment($this);
$this->supportMessages->add($supportMessage);
}
}
Much thanks for any help in advance.
Finally, my colleague found the problem. It was in my controller. I've attached UploadedFile objects there, but in the fact I needed to attach my entity File objects.
Fixed code snipped:
if ($form->isSubmitted() && $form->isValid()) {
$attachments = new ArrayCollection();
foreach ($supportMessage->getAttachments() as $attachment) {
$fileName = $this->get('app.file_uploader')->upload($attachment);
$file = new File();
$file->setFilename($fileName);
$file->setUser($this->getUser());
$attachments->add($file);
//$supportMessage->addAttachment($file);
}
$supportMessage->setAttachments($attachments);
// ... other code here ...
}
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'm trying to do file upload for my Task entity. I working with two sources:
http://symfony.com/doc/3.4/controller/upload_file.html and Symfony2 file upload step by step but I can't figure out how to keep uploaded file during editing.
I'm not sure if I implemented right the part:
My form was complaining when I tried to edit any Task:
The form's view data is expected to be an instance of class
Symfony\Component\HttpFoundation\File\File, but is a(n) string. You
can avoid this error by setting the "data_class" option to null or by
adding a view transformer that transforms a(n) string to an instance
of Symfony\Component\HttpFoundation\File\File.
So I modified getter getBrochure() in my Task entity which I want to expand of PDF file. Please, see below my getBrochure method This is code of my entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
/**
* Task
*
* #ORM\Table(name="task")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TaskRepository")
*/
class Task
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var \DateTime
*
* #ORM\Column(name="datetime", type="datetime")
*/
private $datetime;
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="tasks")
* #ORM\JoinTable(name="categories_tasks")
*/
private $categories;
/**
* #ORM\Column(type="text")
*/
private $description;
/**
* #ORM\Column(type="string")
*
* #Assert\File(mimeTypes={ "application/pdf" })
*/
private $brochure;
public function __construct() {
$this->categories = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Task
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set datetime
*
* #param \DateTime $datetime
*
* #return Task
*/
public function setDatetime($datetime)
{
$this->datetime = $datetime;
return $this;
}
/**
* Get datetime
*
* #return \DateTime
*/
public function getDatetime()
{
return $this->datetime;
}
public function getCategories()
{
return $this->categories;
}
public function setCategories(Category $categories)
{
$this->categories = $categories;
}
public function getDescription()
{
return $this->description;
}
public function setDescription($description)
{
$this->description = $description;
}
public function getBrochure()
{
//return $this->brochure;
return new File($this->brochure);
}
public function setBrochure($brochure)
{
$this->brochure = $brochure;
return $this;
}
public function __toString() {
return $this->name;
}
}
?>
The result is that I can load edit page, but the field of file upload is empty, there is no information that I uploaded any file. I'm not sure if there should be any information but I see in database that the filename is there and also in web folder there is uploaded file. When I change anything in Task and save of file is cleared and when I try to launch edit page I see:
The file "" does not exis
What is clear for me, because file column for this Task was cleared. So, how to keep file during editing when I don't want to upload new file?
This is my TaskType
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Ivory\CKEditorBundle\Form\Type\CKEditorType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class TaskType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name')->add('datetime')->add('categories')
->add('description', 'Ivory\CKEditorBundle\Form\Type\CKEditorType', array())
->add('brochure', FileType::class, array('label' => 'Broszurka (PDF)', 'required' => false));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Task'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_task';
}
}
and this is my TaskController (only edit action)
/**
* Displays a form to edit an existing task entity.
*
* #Route("/{id}/edit", name="task_edit")
* #Method({"GET", "POST"})
*/
public function editAction(Request $request, Task $task)
{
$deleteForm = $this->createDeleteForm($task);
$editForm = $this->createForm('AppBundle\Form\TaskType', $task);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('task_edit', array('id' => $task->getId()));
}
return $this->render('task/edit.html.twig', array(
'task' => $task,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
Task Entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
/**
* Task
*
* #ORM\Table(name="task")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TaskRepository")
*/
class Task
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var \DateTime
*
* #ORM\Column(name="datetime", type="datetime")
*/
private $datetime;
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="tasks")
* #ORM\JoinTable(name="categories_tasks")
*/
private $categories;
/**
* #ORM\Column(type="text")
*/
private $description;
/**
* #ORM\Column(type="string")
*
* #Assert\File(mimeTypes={ "application/pdf" })
*/
private $brochure;
public function __construct() {
$this->categories = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Task
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set datetime
*
* #param \DateTime $datetime
*
* #return Task
*/
public function setDatetime($datetime)
{
$this->datetime = $datetime;
return $this;
}
/**
* Get datetime
*
* #return \DateTime
*/
public function getDatetime()
{
return $this->datetime;
}
public function getCategories()
{
return $this->categories;
}
public function setCategories(Category $categories)
{
$this->categories = $categories;
}
public function getDescription()
{
return $this->description;
}
public function setDescription($description)
{
$this->description = $description;
}
public function getBrochure()
{
//return $this->brochure;
return new File($this->brochure);
}
public function setBrochure($brochure)
{
$this->brochure = $brochure;
return $this;
}
public function __toString() {
return $this->name;
}
}
?>
You should check the file and then,If the user did not select the file,Select the file name from the database
if ($editForm->isSubmitted() && $editForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$form->handleRequest($request);
$TaskRepo=$em->getRepository('AppBundle:Task');
$Taskdata = $TaskRepo->find($id);///id task
$Taskdata->setName($form->get('name')->getData());
$Taskdata->setDescription($form->get('description(')->getData());
$Taskdata->setDatetime(new \DateTime('now'));
if($form->get('brochure')->getData() != ""){////Check the file selection status
$file2 = $form->get('brochure')->getData();
$fileName2 = md5(uniqid()).'.'.$file2->guessExtension();
$file2->move(
$this->getParameter('brochures_directory'), $fileName2);
$Taskdata->setBrochure($fileName2);
}
$em->flush();
}
OK I found the problem few seconds after I wrote comment and added Task entity code. In getBrochure method I tried to create File object for every Task instance even it hasn't any brochure so the solution is to use:
public function getBrochure()
{
return $this->brochure;
//return new File($this->brochure);
}