How to use CollectionType with FosRestBundle - symfony

I'm building an API with the FOSRestBundle and I'm trying to add existing Employee entities to an existing Report entity. But my form validation keeps throwing an error: This form should not contain extra fields
Adding 'allow_add' => true to the ReportType options is not an option because I only want to add excisting employees to the Report. How can I make this work?
PUT request body
{
"report":{
"notes":"This is a note.",
"client":5,
"contactPerson":4,
"contributors":[
{
"id": 10
}
]
}
}
ReportType
class ReportType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('notes')
->add('client', 'entity', array(
'class' => 'ApiBundle:Client'
))
->add('contactPerson', 'entity', array(
'class' => 'ApiBundle:ContactPerson'
))
->add('contributors', CollectionType::class, array(
'entry_type' => EmployeeType::class,
'by_reference' => false
))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'ApiBundle\Entity\Report',
'csrf_protection' => false,
'allow_extra_fields' => true
));
}
}
Snippet from Report entity
/**
* #ORM\ManyToMany(targetEntity="ApiBundle\Entity\Employee", inversedBy="contributions", cascade={"persist"})
* #ORM\JoinTable(name="reports_contributors")
*/
private $contributors;
/**
* Constructor
*/
public function __construct()
{
$this->contributors = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add contributor
*
* #param \ApiBundle\Entity\Employee $contributor
*
* #return Report
*/
public function addContributor(\ApiBundle\Entity\Employee $contributor)
{
$this->contributors[] = $contributor;
return $this;
}
/**
* Remove contributor
*
* #param \ApiBundle\Entity\Employee $contributor
*/
public function removeContributor(\ApiBundle\Entity\Employee $contributor)
{
$this->contributors->removeElement($contributor);
}
/**
* Get contributors
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getContributors()
{
return $this->contributors;
}
Snippet from Employee Entity
/**
* #ORM\ManyToMany(targetEntity="ApiBundle\Entity\Report", mappedBy="contributors")
*/
private $contributions;
public function __construct() {
$this->contributions = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add contribution
*
* #param \ApiBundle\Entity\Report $contribution
*
* #return Employee
*/
public function addContribution(\ApiBundle\Entity\Report $contribution)
{
$this->contributions[] = $contribution;
return $this;
}
/**
* Remove contribution
*
* #param \ApiBundle\Entity\Report $contribution
*/
public function removeContribution(\ApiBundle\Entity\Report $contribution)
{
$this->contributions->removeElement($contribution);
}
/**
* Get contributions
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getContributions()
{
return $this->contributions;
}
Solved it!
No need to use CollectionType. The solution is to use an EntityType with the multiple option.
->add('contributors', 'entity', array(
'class' => 'ApiBundle:Employee',
'multiple' => true
))
PUT request body
{
"report":{
"notes":"This is a note.",
"client":5,
"contactPerson":4,
"contributors":[10,6]
}
}

Related

Vichuploader not work on production server

I have a problem since 2 days! I try to upload file with vichuploaderBundle on a symfony 3.4 project.
I've already done this many times. But this time...It doesn't work and i don't understand why. On my local version, it work fine but on my production server it doesn't work.
Here is the error message:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'image_name' cannot be null
The file entity is persisted (with an id and a created date but the image name is empty???)it's like the vichuploader mapping doesn't work???
I have an Entity (NoteFrais) and each NoteFrais has a one relation with an another Entity (JustificatifDefraiement).
here is my JustificatifDefraiement entity:
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* JustificatifDefraiement
*
* #ORM\Table(name="justificatif_defraiement")
* #ORM\Entity(repositoryClass="MKG\MystiBundle\Repository \JustificatifDefraiementRepository")
* #vich\Uploadable
*/
class JustificatifDefraiement
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="justificatif", fileNameProperty="imageName")
*
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
private $imageName;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
private $updatedAt;
/**
* Constructor
*/
public function __construct()
{
$this->updatedAt = new \DateTime();
}
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|UploadedFile $justificatifDefraiement
* #return JustificatifDefraiement
*/
public function setImageFile(File $justificatifDefraiement = null)
{
$this->imageFile = $justificatifDefraiement;
if ($justificatifDefraiement) {
$this->updatedAt = new \DateTime();
}
return $this;
}
/**
* #return File|null
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
*
* #param $imageName
*
* #return $this
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
return $this;
}
/**
* #return string|null
*/
public function getImageName()
{
return $this->imageName;
}
/**
* Set updatedAt
*
* #param \DateTime $updatedAt
*
* #return JustificatifDefraiement
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Get updatedAt
*
* #return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
}
My form:
class JustificatifDefraiementType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('imageFile', FileType::class, array(
//'data_class' => null,
'label' => false,
'required' => true,
'attr' => array(
'class' => 'NoteFraisBootstrapFileInput',
'type' => 'file',
'placeholder' => 'Selectionner un justificatif (jpeg, png, jpg, pdf)',
'data-preview-file-type' => 'text',
'data-allowed-file-extensions' => '["jpeg", "png", "jpg", "pdf"]',
)
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MKG\MystiBundle\Entity\JustificatifDefraiement'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'mkg_mystibundle_justificatifDefraiement';
}
}
The configuration:
parameters:
locale: fr
app.path.logos: /uploads/logos
app.path.imports: /uploads/imports
app.path.justificatifs: /uploads/justificatifs
I have this relation with another entity:
class NoteFrais
{
//.......//
/**
* #ORM\OneToOne(targetEntity="MKG\MystiBundle\Entity\JustificatifDefraiement", cascade={"persist"})
* #ORM\JoinColumn(name="justificatif_defraiement_id", referencedColumnName="id", onDelete="CASCADE", nullable=true)
*/
private $justificatifDefraiement;
//.......//
}
And the noteFraisType:
class NoteFraisType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//.......//
->add('justificatifDefraiement', JustificatifDefraiementType::class, array(
'required' => false));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MKG\MystiBundle\Entity\NoteFrais'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'mkg_mystibundle_notefrais';
}
}
Please help me!!
When you use VichUploader then you must use VichFileType inside your FormType
Then your buildForm ...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('imageFile', VichFileType::class, array(
//Options ...
));
}

Could not determine access type for property "file"

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

Symfony2 forms and many to many relation

(Symfony version 2.7)
Hi, I have a problem with form in field with many to many relation.
Class Notification {
public function __construct()
{
$this->assigneduser = new \Doctrine\Common\Collections\ArrayCollection();
$this->flags = new ArrayCollection();
}
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Flag", inversedBy="notificationflags", cascade={"persist"})
* #ORM\JoinTable(name="sla_notificationflags",
* joinColumns={#ORM\JoinColumn(name="notification_id", referencedColumnName="notificationId")},
* inverseJoinColumns={#ORM\JoinColumn(name="flag_id", referencedColumnName="flagId")}
* )
*
*/
private $flags;
/**
* Add flag
*
* #param \AppBundle\Entity\Flag $flag
* #return Notification
*/
public function addFlag(Flag $flag)
{
$flag->addNotificationflag($this);
$this->flags[] = $flag;
return $this;
}
}
Class Flag {
public function __construct()
{
$this->notificationflags = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Notification", mappedBy="flags")
*/
protected $notificationflags;
/**
* Add notificationflags
*
* #param \AppBundle\Entity\Notification $notificationflag
* #return Flag
*/
public function addNotificationflag(Notification $notificationflag)
{
if(!$this->notificationflags->contains($notificationflag)) {
$this->notificationflags->add($notificationflag);
}
return $this;
}
}
My Form Class
class NotificationSingleFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('flags','entity',array(
'label' => false,
'attr' => array(
'class' => 'select'
),
'class' => 'AppBundle\Entity\Flag',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('p')
->addOrderBy('p.name','ASC');
},
'property' => 'name',
'required' => false
)
);
}
}
When i send the form i see error:
Neither the property "flags" nor one of the methods
"addFlag()"/"removeFlag()", "setFlags()", "flags()", "__set()" or
"__call()" exist and have public access in class
"AppBundle\Entity\Notification".
You have to genrate getters/setters and add/remove methods of flag attribute.
use php app/console doctrine:generate:entities to generate them automatically

symfony2 embedded form won't persist to database

I followed the documentation for this here: but could not get the example to persist to the database for the embedded form; the Plant class saved just fine. I am under the assumption that the persist and flush methods in the controller handle the persisting of both entities. Is this wrong to assume? Do I need to intercept it and set it manually in the controller before flush?
At any rate, here is my code:
Plant Entity:
<?php
/**
* #ORM\Entity(repositoryClass="Blogger\BlogBundle\Entity\Repository\PlantRepository")
* #ORM\Table(name="plant")
* #ORM\HasLifecycleCallbacks
*/
class Plant
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="array", nullable=true)
* #ORM\ManyToMany(targetEntity="Blogger\BlogBundle\Entity\Picture", inversedBy="plants", cascade={"persist"})
* #ORM\JoinTable(name="picture")
*/
protected $pictures;
//...
public function __construct()
{
$this->pictures = new ArrayCollection;
}
//...
/**
* Add pictures
*
* #param \Blogger\BlogBundle\Entity\Picture $pictures
* #return Plant
*/
public function addPicture(\Blogger\BlogBundle\Entity\Picture $pictures)
{
$pictures->addPlant($this);
$this->pictures[] = $pictures;
}
/**
* Remove pictures
*
* #param \Blogger\BlogBundle\Entity\Picture $pictures
*/
public function removePicture(\Blogger\BlogBundle\Entity\Picture $pictures)
{
$this->pictures->removeElement($pictures);
}
/**
* Get pictures
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPictures()
{
return $this->pictures;
}
}
Picture Entity:
<?php
namespace Blogger\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="Picture")
*/
class Picture
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
//...
/**
* #ORM\Column(type="text")
*/
public $path;
/**
* #ORM\Column(type="array", nullable=true)
* #ORM\ManyToMany(targetEntity="Blogger\BlogBundle\Entity\Plant", mappedBy="pictures")
*/
private $plants;
/**
* Constructor
*/
public function __construct()
{
$this->plants = new \Doctrine\Common\Collections\ArrayCollection();
}
//...
/**
* Set path
*
* #param string $path
* #return Picture
*/
public function setPath($path)
{
$this->path = $path;
return $this;
}
/**
* Get path
*
* #return string
*/
public function getPath()
{
return $this->path;
}
/**
* Add plants
*
* #param \Blogger\BlogBundle\Entity\Plant $plants
* #return Picture
*/
public function addPlant(\Blogger\BlogBundle\Entity\Plant $plants)
{
if (!$this->plants->contains($plants)) {
$this->plants->add($plants);
}
}
/**
* Remove plants
*
* #param \Blogger\BlogBundle\Entity\Plant $plants
*/
public function removePlant(\Blogger\BlogBundle\Entity\Plant $plants)
{
$this->plants->removeElement($plants);
}
/**
* Get plants
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPlants()
{
return $this->plants;
}
}
Plant Form:
<?php
namespace Blogger\BlogBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PlantForm extends AbstractType
{
public function __construct($em) {
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
//...
$builder->add('pictures', 'collection', array(
'type' => new PictureForm(),
'options' => array(
'data_class' => 'Blogger\BlogBundle\Entity\Picture'),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Blogger\BlogBundle\Entity\Plant',
));
}
public function getName()
{
return 'plant';
}
}
Picture Form:
<?php
namespace Blogger\BlogBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PictureForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
//...
$builder->add('path', 'textarea');
//...
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Blogger\BlogBundle\Entity\Picture',
));
}
public function getName()
{
return 'picture';
}
}
Plant Controller:
public function newAction(Request $request){
$plant = new Plant();
$image1 = new Picture();
$plant->getPictures()->add($image1);
$form = $this->createForm(new PlantForm($this->getDoctrine()->getManager()), $plant);
if ($request->getMethod() == 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($plant);
$em->flush();
return $this->redirect($this->generateUrl('route', array(
'id' => $plant->getId()
)));
}
}
return $this->render('Bundle:Plant:new.html.twig', array(
'form' => $form->createView()
));
}
I suspect I don't have my annotations for the database mapped correctly. When I open phpadmin, there are no relationships defined in the database.
I figured it out. It was indeed an error with my annotations. This question was actually answered in a post here. For my solution, it looked like this:
Plant entity:
class Plant
{
//..
/**
* #ORM\ManyToMany(targetEntity="Blogger\BlogBundle\Entity\Picture", inversedBy="plants", cascade={"persist"})
* #ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $pictures;
/**
* Constructor
*/
public function __construct()
{
$this->pictures = new ArrayCollection();
}
public function addPicture(\Blogger\BlogBundle\Entity\Picture $pictures)
{
$pictures->addPlant($this);
$this->pictures[] = $pictures;
}
//..
}
class Picture
{
//..
/**
* #ORM\ManyToMany(targetEntity="Blogger\BlogBundle\Entity\Plant", mappedBy="pictures", cascade={"persist", "remove"})
*/
private $plants;
/**
* Constructor
*/
public function __construct()
{
$this->plants = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addPlant(\Blogger\BlogBundle\Entity\Plant $plants)
{
if (!$this->plants->contains($plants)) {
$this->plants->add($plants);
}
}
//..
}
Controller:
public function newAction(Request $request){
$plant = new Plant();
if ($request->getMethod() == 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($plant);
$em->flush();
return $this->redirect($this->generateUrl('BloggerBlogBundle_plant_library_show', array(
'id' => $plant->getId()
)));
}
}
return $this->render('BloggerBlogBundle:Library:new.html.twig', array(
'form' => $form->createView()
));
}
Maybe a useful note to others: this mapping created a new table called plant_picture which holds the association. I was surprised to learn that a "picture" column was NOT added to the Plant entity at all.

Bidirectionnal relation in form

i'm having difficulties to save a form that contains a collection from a OneToMany bidirectionnal relation.
I got the following error :
An exception occurred while executing 'INSERT INTO fieldsgroup (title, colloque_id) VALUES (?, ?)' with params ["groupe 1", null]:
Here are the queries that the profiler gives me :
START TRANSACTION;
INSERT INTO colloque (title)
VALUES
('titre du colloque');
INSERT INTO fieldsgroup (title, colloque_id)
VALUES
('titre de mon groupe de champs', null)
ROLLBACK
My controller :
public function createColloqueAction()
{
$colloque = new Colloque();
$form = $this->createForm(new ColloqueType, $colloque);
$request = $this->get('request');
if ($request->getMethod() == 'POST'){
$form->bind($request);
if ($form->isValid()) {
$this->get('session')->getFlashBag()->add('notice', 'Colloque correctement enregistré');
$em = $this->getDoctrine()->getManager();
$em->persist($colloque);
$em->flush();
return $this->redirect($this->generateUrl('ptolemee_admin_homepage'));
}
}
return $this->render('PtolemeeAdminBundle:Admin:createColloque.html.twig',array(
'form' => $form->createView()
));
}
My "Colloque" entity :
class Colloque
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Ptolemee\ColloqueBundle\Entity\FieldsGroup", mappedBy="colloque", cascade={"persist"})
*/
private $fieldsGroups;
public function __construct()
{
$this->fieldsGroups = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add fieldsGroups
*
* #param \Ptolemee\ColloqueBundle\Entity\FieldsGroup $fieldsGroups
* #return Colloque
*/
public function addFieldsGroup(\Ptolemee\ColloqueBundle\Entity\FieldsGroup $fieldsGroups)
{
$this->fieldsGroups[] = $fieldsGroups;
$fieldsGroups->setColloque($this);
return $this;
}
/**
* Remove fieldsGroups
*
* #param \Ptolemee\ColloqueBundle\Entity\FieldsGroup $fieldsGroups
*/
public function removeFieldsGroup(\Ptolemee\ColloqueBundle\Entity\FieldsGroup $fieldsGroups)
{
$this->fieldsGroups->removeElement($fieldsGroups);
}
/**
* Get fieldsGroups
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getFieldsGroups()
{
return $this->fieldsGroups;
}
}
Its form, ColloqueType :
class ColloqueType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text')
->add('date', 'datetime')
->add('successMessage', 'textarea')
->add('fieldsGroups', 'collection', array( 'type' => new FieldsGroupType(),
'allow_add' => true,
'allow_delete' => true))
;
}
}
My class FieldsGroup
class FieldsGroup
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Ptolemee\ColloqueBundle\Entity\Colloque", inversedBy="fieldsGroups")
* #ORM\JoinColumn(nullable=false)
*/
private $colloque;
/**
* Set colloque
*
* #param \Ptolemee\ColloqueBundle\Entity\Colloque $colloque
* #return FieldsGroup
*/
public function setColloque(\Ptolemee\ColloqueBundle\Entity\Colloque $colloque)
{
$this->colloque = $colloque;
return $this;
}
/**
* Get colloque
*
* #return \Ptolemee\ColloqueBundle\Entity\Colloque
*/
public function getColloque()
{
return $this->colloque;
}
}
And its form, FieldsGroupType :
class FieldsGroupType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text')
/*->add('fields', 'collection', array('type' => new FieldType(),
'allow_add' => true,
'allow_delete' => true))*/
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Ptolemee\ColloqueBundle\Entity\FieldsGroup'
));
}
/**
* #return string
*/
public function getName()
{
return 'ptolemee_colloquebundle_fieldsgroup';
}
}
I know this should work properly without any more perist(), or anything else...
Any help would be highly appreciated, i've been working on that for hours without finding what's the right way to do....
Thanks a lot !
Just add setFieldsGroup(ArrayCollection) to your entity
public function addFieldsGroup(\Ptolemee\ColloqueBundle\Entity\FieldsGroup $fieldsGroups)
{
$this->fieldsGroups[] = $fieldsGroups;
$fieldsGroups->setColloque($this);
return $this;
}
public function setFieldsGroup(ArrayCollection $fieldsGroups)
{
foreach ($fieldsGroups as $fieldsGroup) {
$fieldsGroup->setColloque($this);
}
$this->fieldsGroups = $fieldsGroups;
}
Adding setFieldsGroup(ArrayCollection) is working but it's a trick.
Simply add , cascade={"persist"} to your ManyToOne field
/**
* #ORM\ManyToOne(targetEntity="Ptolemee\ColloqueBundle\Entity\Colloque", inversedBy="fieldsGroups", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*/
private $colloque;
PS1 :
Prefer handleRequest() instead of $request->getMethod() == 'POST' and $form->bind($request) if you have Symfony2.x x>=3.
PS2 :
You can get request like this when needed (more elegant way) :
public function createColloqueAction(Request $request)

Resources