Im trying to upload a file using SonataAdminBundle.
I don't know what am I doing wrong as I followed instructions from official documentation https://sonata-project.org/bundles/admin/3-x/doc/cookbook/recipe_file_uploads.html
The upload() function is not triggering, and even when I invoke the method in DocumentAdmin it does not put the files in the directory I specified.
It seems like the yaml file is not even read, but how am I supposed to configure it ?
prePersist() and preUpdate() are triggered even without it.
Code:
final class DocumentAdmin extends AbstractAdmin
{
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->add('title', null, [
'label' => 'Name'
])
->add('documentCategory', null, [
'label' => 'Typ'
])
->add('priority', null, [
'label' => 'Order'
])
;
}
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('file', FileType::class, [
'required' => true,
])
->add('title', null, [
'label' => 'Name'
])
->add('priority', null, [
'label' => 'Priority'
])
->add('documentCategory', null, [
'label' => 'Typ'
])
;
}
public function prePersist($document)
{
$this->manageFileUpload($document);
}
public function preUpdate($document)
{
$this->manageFileUpload($document);
}
private function manageFileUpload($document)
{
if ($document->getFile()) {
$document->refreshUpdated();
}
}
public function toString($object)
{
return $object instanceof Document
? $object->getTitle()
: 'File'; // shown in the breadcrumb on the create view
}
}
Document Entity
class Document
{
const SERVER_PATH_TO_DOCUMENTS_FOLDER = '%kernel.project_dir%/public/uploads';
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* Unmapped property to handle file uploads
*/
private $file;
/**
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
}
/**
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
/**
* Manages the copying of the file to the relevant place on the server
*/
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->getFile()) {
return;
}
// we use the original file name here but you should
// sanitize it at least to avoid any security issues
// move takes the target directory and target filename as params
$file = $this->getFile();
$directory = self::SERVER_PATH_TO_DOCUMENTS_FOLDER;
$originaName = $this->getFile()->getClientOriginalName();
// dump($file);
// dump($directory);
// dump($originaName);
// die();
$file->move($directory, $originaName);
// set the path property to the filename where you've saved the file
// clean up the file property as you won't need it anymore
$this->setFile(null);
}
/**
* Lifecycle callback to upload the file to the server.
*/
public function lifecycleFileUpload()
{
$this->upload();
}
/**
* Updates the hash value to force the preUpdate and postUpdate events to fire.
*/
public function refreshUpdated()
{
$this->setDateOfUpload(new \DateTime());
}
/**
* #ORM\Column
* #Gedmo\UploadableFileName
*/
private $title;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $dateOfUpload;
/**
* #ORM\Column(type="smallint")
*/
private $priority;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\DocumentCategory", inversedBy="documents")
* #ORM\JoinColumn(nullable=false)
*/
private $documentCategory;
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
/**
* #return \DateTime
*/
public function getDateOfUpload()
{
return $this->dateOfUpload;
}
public function setDateOfUpload(\DateTimeInterface $dateOfUpload): self
{
$this->dateOfUpload = new \DateTime();
return $this;
}
public function getPriority(): ?int
{
return $this->priority;
}
public function setPriority(int $priority): self
{
$this->priority = $priority;
return $this;
}
public function getDocumentCategory(): ?DocumentCategory
{
return $this->documentCategory;
}
public function setDocumentCategory(?DocumentCategory $documentCategory): self
{
$this->documentCategory = $documentCategory;
return $this;
}
// public function myCallbackMethod(array $info)
// {
// }
public function __toString()
{
return $this->title;
}
}
EDIT: I changed the file directory path to:
const SERVER_PATH_TO_DOCUMENTS_FOLDER = 'uploads/documents';
And also invoked the lifecycleFileUpload() method in manageFileUpload() method
and now the files are moved to the directory.
Is it the right way to upload ?
Related
The flush function does not add anything to the database even though it doesn't return any error.
Any idea of how can I solve that?
Thanks in advance for your help!
Here is my code:
Controller:
#[Route("/messages")]
public function create(Request $request, ManagerRegistry $signup, UserRepository $userRepository, MessageRepository $messageRepository): Response
{
$message = new Message();
$userRepository = $signup->getRepository(User::class);
$formdico = [];
foreach ($userRepository->findAll() as $user) {
$userform = $this->createForm(MessageType::class, $message);
$formdico[$user->getId()] = $userform;
}
foreach ($formdico as $form) {
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$newMessage = new Message();
$newMessage->setSender($userRepository->findOneById($form->getData()->getSender()));
$newMessage->setReceiver($userRepository->findOneById($form->getData()->getSender()));
$newMessage->setContent($form->getData()->getContent());
$newMessage->setCreatedAt(new \DateTime('now'));
$em = $signup->getManager();
$em->persist($newMessage);
$em->flush($newMessage);
}
}
foreach ($userRepository->findAll() as $user) {
$userform = $this->createForm(MessageType::class, $message);
$formdico[$user->getId()] = $userform->createView();
}
return $this->render('message/readAllUser.html.twig', ['users' => $userRepository->findAll(), "formdico" => $formdico, 'messages' => $messageRepository->findAll()]);
}
Entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity()]
class Message
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "AUTO")]
#[ORM\Column(type: "integer")]
private int $id;
#[ORM\ManyToOne(targetEntity: "App\Entity\User", inversedBy: "sendedMessages")]
private $sender;
#[ORM\ManyToOne(targetEntity: "App\Entity\User", inversedBy: "receivedMessages")]
private $receiver;
#[ORM\Column(type: "text")]
private string $content;
#[ORM\Column(type: "date")]
private \DateTime $createdAt;
/**
* Get the value of id
*/
public function getId()
{
return $this->id;
}
/**
* Set the value of id
*
* #return self
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Get the value of sender
*/
public function getSender()
{
return $this->sender;
}
/**
* Set the value of sender
*
* #return self
*/
public function setSender($sender)
{
$this->sender = $sender;
return $this;
}
/**
* Get the value of receiver
*/
public function getReceiver()
{
return $this->receiver;
}
/**
* Set the value of receiver
*
* #return self
*/
public function setReceiver($receiver)
{
$this->receiver = $receiver;
return $this;
}
/**
* Get the value of content
*/
public function getContent()
{
return $this->content;
}
/**
* Set the value of content
*
* #return self
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get the value of createdAt
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Set the value of createdAt
*
* #return self
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
}
Form:
<?php
namespace App\Form;
use App\Entity\Message;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
class MessageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('sender', IntegerType::class, [])
->add('content', TextType::class, ['label' => false, 'attr' => ['placeholder' => 'Message Content']])
->add('receiver', IntegerType::class, []);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(['data_class' => Message::class,]);
}
}
I tried removing the persist, dumped a lot of things to try understanding the problem but I can't get to solve this...
And the form is valid
I'm trying to show preview image with Sonata Admin Bundle 3 version but I can't do it. I get this error in RecipeAdmin.php: Could not load type "file": class does not exist.
RecipeAdmin.php
<?php
declare(strict_types=1);
namespace App\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Sonata\Form\Type\CollectionType;
use Sonata\AdminBundle\Form\Type\ModelListType;
final class RecipeAdmin extends AbstractAdmin
{
protected function configureDatagridFilters(DatagridMapper $datagridMapper): void
{
$datagridMapper
->add('title',null,['label' =>'Título'])
->add('image',null,['label' =>'Imagen'])
->add('description',null,['label' =>'Descripción'])
->add('score',null,['label' =>'Puntuación'])
->add('visible')
;
}
protected function configureListFields(ListMapper $listMapper): void
{
$listMapper
->add('id')
->add('user', CollectionType::class,['label' =>'Usuario'])
->add('title',null,['label' =>'Título'])
->add('image',null,['label' =>'Imagen'])
->add('description',null,['label' =>'Descripción'])
->add('score',null,['label' =>'Puntuación'])
->add('visible',null,['label' =>'Visible'])
->add('_action', null, [
'label' => 'Acciones',
'actions' => [
'show' => [],
'edit' => [],
'delete' => [],
],
]);
}
protected function configureFormFields(FormMapper $formMapper): void
{
$image = $this->getSubject();
// use $fileFormOptions so we can add other options to the field
$fileFormOptions = ['required' => false];
if ($image && ($webPath = $image->getImage())) {
// get the request so the full path to the image can be set
$request = $this->getRequest();
$fullPath = $request->getBasePath().'/'.$webPath;
// add a 'help' option containing the preview's img tag
$fileFormOptions['help'] = '<img src="'.$fullPath.'" class="admin-preview"/>';
$fileFormOptions['help_html'] = true;
}
$formMapper
->add('title',null,['label' =>'Título'])
->add('image', 'file', $fileFormOptions)
->add('description',null,['label' =>'Descripción'])
->add('score',null,['label' =>'Puntuación'])
->add('visible')
;
}
protected function configureShowFields(ShowMapper $showMapper): void
{
$showMapper
->add('title',null,['label' =>'Título'])
->add('image',null,['label' =>'Imagen'])
->add('description',null,['label' =>'Descripción'])
->add('user', CollectionType::class)
->add('score',null,['label' =>'Puntuaciones'])
->add('visible')
;
}
}
Recipe.php Entity
<?php
namespace App\Entity;
use App\Repository\RecipeRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=RecipeRepository::class)
*/
class Recipe
{
public function __construct() {
$this->categories = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $title;
/**
* #ORM\Column(type="string", length=255)
*/
private $image;
/**
* #ORM\Column(type="string", length=255)
*/
private $description;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="recipes")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $score;
/**
* #ORM\Column(type="boolean")
*/
private $visible;
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="recipe", cascade={"persist"})
* #ORM\JoinTable(name="recipes_categories")
*/
private $categories;
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id)
{
$this->id = $id;
return $this;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getImage(): ?string
{
return $this->image;
}
public function setImage(string $image): self
{
$this->image = $image;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(string $description): self
{
$this->description = $description;
return $this;
}
public function getUser()
{
return $this->user;
}
public function setUser($user)
{
$this->user = $user;
return $this;
}
public function getScore(): ?int
{
return $this->score;
}
public function setScore(?int $score): self
{
$this->score = $score;
return $this;
}
public function getVisible(): ?bool
{
return $this->visible;
}
public function setVisible(bool $visible): self
{
$this->visible = $visible;
return $this;
}
public function getCategories()
{
return $this->categories;
}
public function setCategories($categories)
{
$this->categories = $categories;
return $this;
}
public function __toString()
{
return $this->getTitle();
}
public function addCategory(Category $category): self
{
if (!$this->categories->contains($category)) {
$this->categories[] = $category;
}
return $this;
}
public function removeCategory(Category $category): self
{
$this->categories->removeElement($category);
return $this;
}
}
This is the link about how do it: https://symfony.com/doc/current/bundles/SonataAdminBundle/cookbook/recipe_image_previews.html
https://sonata-project.org/bundles/admin/master/doc/cookbook/recipe_image_previews.html#showing-image-previews
In the documentation explains that I have to use 'file' type fields but when I use it in my proyect doesn't runs.
This is an error in the doc, instead of file you should use FileType::class and adding :
use Symfony\Component\Form\Extension\Core\Type\FileType;
$formMapper
->add('title',null,['label' =>'Título'])
->add('image', FileType::class, $fileFormOptions)
You will still have error such as :
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.
In the cookbook they tell you to create an Image Entity so you should add it and follow all the steps :
https://sonata-project.org/bundles/admin/master/doc/cookbook/recipe_file_uploads.html
I suggest instead of following this cookbook, you should install and use the sonata media, the integration is easier and it have some nice features such as making different formats for your upload.
I have problem with query builder using Doctrine 2 in Symfony 4.4 and Omines/Datatables Bundle.
I have two entities, User and Log, which look like this:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=50)
*/
private $firstname;
/**
* #ORM\Column(type="string", length=50)
*/
private $lastname;
/**
* #ORM\OneToMany(targetEntity=Log::class, mappedBy="user")
*/
private $logs;
public function __construct()
{
$this->logs = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getFirstname(): ?string
{
return $this->firstname;
}
public function setFirstname(string $firstname): self
{
$this->firstname = $firstname;
return $this;
}
public function getLastname(): ?string
{
return $this->lastname;
}
public function setLastname(string $lastname): self
{
$this->lastname = $lastname;
return $this;
}
/**
* #return Collection|Log[]
*/
public function getLogs(): Collection
{
return $this->logs;
}
public function addLog(Log $log): self
{
if (!$this->logs->contains($log)) {
$this->logs[] = $log;
$log->setUser($this);
}
return $this;
}
public function removeLog(Log $log): self
{
if ($this->logs->contains($log)) {
$this->logs->removeElement($log);
// set the owning side to null (unless already changed)
if ($log->getUser() === $this) {
$log->setUser(null);
}
}
return $this;
}
}
Entity Log:
<?php
namespace App\Entity;
use App\Repository\LogRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=LogRepository::class)
*/
class Log
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $logStart;
/**
* #ORM\Column(type="string", length=15)
*/
private $ip;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="logs")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $user;
public function getId(): ?int
{
return $this->id;
}
public function getLogStart(): ?\DateTimeInterface
{
return $this->logStart;
}
public function setLogStart(\DateTimeInterface $logStart): self
{
$this->logStart = $logStart;
return $this;
}
public function getIp(): ?string
{
return $this->ip;
}
public function setIp(string $ip): self
{
$this->ip = $ip;
return $this;
}
public function getUser(): ?user
{
return $this->user;
}
public function setUser(?user $user): self
{
$this->user = $user;
return $this;
}
}
I also use the omines/datatables bundle (Documentation and link to github)
I tried to build a query with a left join to my User entity. My code in the controller is as following:
$table = $this->datatableFactory->create([])
->add('id', TextColumn::class, ['label' => '#', 'className' => 'bold', 'searchable' => true])
->add('firstname lastname', TextColumn::class, ['label' => $translator->trans('Customer name'), 'className' => 'bold', 'searchable' => true])
->add('logStart', DateTimeColumn::class, ['label' => $translator->trans('Time'), 'className' => 'bold', 'searchable' => false])
->createAdapter(ORMAdapter::class, [
'entity' => Log::class,
'query' => function (QueryBuilder $queryBuilder) {
$queryBuilder
->select('l, u.firstname, u.lastname')
->from(Log::class, 'l')
->leftJoin(User::class, 'u', Join::ON, 'l.user = u');
}
]);
$table->handleRequest($request);
if ($table->isCallback()) {
return $table->getResponse();
}
And I got the following error: Syntax Error line 0, col 60: Error: Expected end of string, got 'ON'
But when I changed the following: ->leftJoin(User::class, 'u', Join::ON, 'l.user = u');
to: ->leftJoin(User::class, 'u', Join::WITH, 'l.user = u.id'); I get the following error:
Cannot read property "id" from an array. Maybe you intended to write
the property path as "[id]" instead.
Does anyone have an idea what I'm doing wrong?
Thank you for every help :)
EDIT:
I found a solution on github and I changed my code to:
->createAdapter(ORMAdapter::class, [
'hydrate' => \Doctrine\ORM\Query::HYDRATE_ARRAY,
'entity' => Log::class,
'query' => function (QueryBuilder $queryBuilder) {
$queryBuilder
->select('l, u')
->from(Log::class, 'l')
->leftJoin('l.user', 'u');
}
]);
but this didn't change anything for me. I still don't have access to the User entity (in this case for example firstname and lastname).
Problem solved. In many to one relation we must use 'field' option in column. For example:
$table = $dataTableFactory->create()
->add('firstName', TextColumn::class, ['label' => 'Firstname', 'field' => 'user.firstname'])
->add('logStart', DateTimeColumn::class, ['label' => 'Log start'])
->createAdapter(ORMAdapter::class, [
'entity' => Log::class,
])
->handleRequest($request);
I have an entity Item and and entity File
When I save my item form with files everything is ok but later, when I try to add a new File to my Item then all the related files get deleted from my table and I only get the newly added items.
The deletion is happening at $form->handleRequest($request); I tried dumping data before and after and can confirm 100% the deletion is happening there.
My $itemEntity is then pre-filled with the existing files but disappear after the handleRequest in $form
I am also using add method instead of set and have an arrayCollection in my entity
I looked into the profiler and can see a doctrine SQL request for deletion happening
This seems like a really weird behaviour to me; I was not deleting the item and I don't even have orphanRemoval or cascade{delete}.
In the end I had to manually recreate records of the old files in the database but that seems inappropriate and I am sure I am missing something.
the million dollar question ?
CONTROLLER
$itemEntity = $em->getRepository(Item::class)->findOneBy([
'uid' => $uid,
]);
// create form
$form = $this->createForm(NewItemType::class, $itemEntity);
// if I dump Item Entity here I can see my old files
$form->handleRequest($request);
// if I dump Item Entity here they are gone and replaced by the new ones
// .... later but never reached
$item->addFile($fileInstance);
ITEM ENTITY:
/**
* #ORM\OneToMany(targetEntity="App\Entity\File", mappedBy="item", cascade={"persist"})
*/
private $file;
public function __construct()
{
$this->file = new ArrayCollection();
}
FILE ENTITY:
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $path;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Item", inversedBy="file")
* #ORM\JoinColumn(nullable=false)
*/
private $item;
/**
* #ORM\Column(type="string", length=255)
*/
private $uid;
/**
* #ORM\Column(type="integer")
*/
private $type;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $description;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $position;
/**
* #var boolean
* #ORM\Column(type="boolean", nullable=true)
*/
private $main;
/**
* #var UploadedFile|null
*/
protected $file;
public function getFile(): ?UploadedFile
{
return $this->file;
}
public function setFile(UploadedFile $file): void
{
$this->file = $file;
}
public function getId(): ?int
{
return $this->id;
}
public function getPath(): ?string
{
return $this->path;
}
public function setPath(string $path): self
{
$this->path = $path;
return $this;
}
public function getUid(): ?string
{
return $this->uid;
}
public function setUid(string $uid): self
{
$this->uid = $uid;
return $this;
}
public function getItem(): ?Item
{
return $this->item;
}
public function setBike(?Item $item): self
{
$this->item = $item;
return $this;
}
public function getType(): ?int
{
return $this->type;
}
public function setType(int $type): self
{
$this->type = $type;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getPosition(): ?int
{
return $this->position;
}
public function setPosition(?int $position): self
{
$this->position = $position;
return $this;
}
public function getMain(): ?bool
{
return $this->main;
}
public function setMain(?bool $main): self
{
$this->main = $main;
return $this;
}
FORM COLLECTION TYPE
->add('item', CollectionType::class, array(
'label' => false,
'entry_type' => ItemFileType::class,
'error_bubbling' => false,
'entry_options' => [ 'required' => false, 'error_bubbling' => true ],
'allow_add' => true,
'allow_delete' => true
))
ITEM FILE TYPE
->add('file', FileType::class, [
'label' => false,
'required' => false
])
->add('description')
->add('main', CheckboxType::class,[
'label' => 'Make this one the main picture',
'required' => false,
]);
This is a common situation when it comes to ArrayCollection. First of all, you have $file variable in Item entity
/**
* #ORM\OneToMany(targetEntity="App\Entity\File", mappedBy="item", cascade={"persist"})
*/
private $file;
In fact, better call this variable $files, as long as it possible to attach a couple or more files to a single item.
And you should also have the following line in your code
public function __construct() {
$this->files = new ArrayCollection();
}
The problem here is that when you call handleRequest it completely rewrite your
$files property. As I think, this happens because you have just set method in your Item entity, while you should not have set method, but add and remove
public function addFile(File $file)
{
if (!$this->files->contains($file)) {
$file->setItem($this);
$this->files->add($file);
}
return $this;
}
public function removeFile(File $file)
{
$this->files->removeElement($file);
return $this;
}
The point is not to completely rewrite ArrayCollection, but extend it. If you remove some item from a collection Doctrine will remove it from the database.
FYI This also may happen because $files collection was not pre-filled with an existing collection before submitting form
I have a little problem with my image upload, can u help me please:
Could not determine access type for property "file".
Controller
/**
* Creates a new Produits entity.
*
*/
public function createAction(Request $request)
{
$entity = new Produits();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('adminProduits_show', array('id' => $entity->getId())));
}
return $this->render('EcommerceBundle:Administration:Produits/layout/new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
/**
* Creates a form to create a Produits entity.
*
* #param Produits $entity The entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(Produits $entity)
{
$form = $this->createForm(ProduitsType::class, $entity);
$form->add('submit', SubmitType::class, array('label' => 'Ajouter'));
return $form;
}
/**
* Displays a form to create a new Produits entity.
*
*/
public function newAction()
{
$entity = new Produits();
$form = $this->createCreateForm($entity);
return $this->render('EcommerceBundle:Administration:Produits/layout/new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
Form
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file', FileType::class, array('data_class' => null))
->add('name', TextType::class)
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Ecommerce\EcommerceBundle\Entity\Media'
));
}
/**
* #return string
*/
public function getName()
{
return 'ecommerce_ecommercebundle_media';
}
Entity
/**
* #ORM\Column(name="name",type="string",length=255)
* #Assert\NotBlank()
*/
private $name;
/**
* #ORM\Column(type="string",length=255, nullable=true)
*/
private $path;
/**
* #Assert\File(
* maxSize = "1024k",
* mimeTypes = {"image/png", "image/jpg", "image/bmp"},
* mimeTypesMessage = "Please upload a valid PDF"
* )
*/
public $file;
public function getUploadRootDir()
{
return __dir__.'/../../../../web/uploads';
}
public function getAbsolutePath()
{
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->path;
}
public function getAssetPath()
{
return 'uploads/'.$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()
{
if (null !== $this->file) {
$this->file->move($this->getUploadRootDir(),$this->path);
unset($this->file);
if ($this->oldFile != null) unlink($this->tempFile);
}
}
/**
* #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;
}
public function getName()
{
return $this->name;
}
public function getFile()
{
return $this->file;
}
/**
* Set path
*
* #param string $path
* #return String
*/
public function setPath($path)
{
$this->path = $path;
return $this;
}
/**
* Set alt
*
* #param string $alt
* #return String
*/
public function setAlt($alt)
{
$this->alt = $alt;
return $this;
}
/**
* Set name
*
* #param string $name
* #return String
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Set updateAt
*
* #param \DateTime $updateAt
*
* #return Media
*/
public function setUpdateAt($updateAt)
{
$this->updateAt = $updateAt;
return $this;
}
/**
* Get updateAt
*
* #return \DateTime
*/
public function getUpdateAt()
{
return $this->updateAt;
}
Thanks for your help guys :)
Please add "'mapped' => false," to form builder.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file', FileType::class,
array(
'data_class' => null,
'mapped' => false,
))
->add('name', TextType::class)
;
}
Those who say that it is wrong to dissolve, see the test there. I'm not the one who made it wrong.
Test code:
https://github.com/symfony/property-access/blob/master/Tests/PropertyAccessorCollectionTest.php#L151
A second solution is to add function setXxx to the property that gives an error in class Entity.
public $xxx;
public function setXxx(Array $xxx)
{
$this->xxx = $xxx;
}
Or
public function __construct()
{
$this->xxx = new ArrayCollection();
}
Video Link: https://knpuniversity.com/screencast/doctrine-relations/create-genus-note
My English is bad, I can tell you.
Setting mapped => false is not the real solution, because the field IS mapped to the entity.
In my case, I have a OneToMany relation, so I need the field mapped in order to use the 'cascase' => {"all"} option.
If the field is not mapped, then you must to persist the related entity manually. That's no good.
Stumbled on this while searching solution for my problem, that was shooting the same error and I was using also ArrayCollection class, so this may be helpful to someone:
My field in ArrayCollection is called $files, so I had to add constructor like this:
use Doctrine\Common\Collections\ArrayCollection;
public function __construct()
{
$this->files = new ArrayCollection();
}
Then I added add and remove methods, like this:
public function addFile(MyFile $file) : self
{
$file->setParentMyItem($this); // Setting parent item
$this->files->add($file);
return $this;
}
public function removeFile(MyFile $file) : self
{
$this->files->removeElement($file);
return $this;
}
But catch is that even my field name was $files I had to name add and remove methods addFile() and removeFile(), without 's' at end, which is totally not logical to me, but that solved the problem.
Same error for me but on a OneToMany relation. Although setting "mapped => false" also solved the error there is another option. I only had a getter, adder and remover method on the entity. I needed to add a "setter" too
In my case it was:
/**
* #param ArrayCollection|SubStatusOptions[]
*/
public function setSubStatusOptions(ArrayCollection $subStatusOptions)
{
$this->subStatusOptions = $subStatusOptions;
}
This happened because I had an entity property but the getter/setters were missing. I used:
php bin/console make:entity --regenerate MyBundle\\Entity\\MyEntity
to regenerate them