(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
Related
I uses two entitys: stagiaire and entreprise. Each stagiaire have one entreprise. I just need to save the id entreprise in the stagiaire table.
When I create a new stagiaire, choose an enterprise for him and save the form, I have the following error
"Expected argument of type "integer" in the stagiaire controller:
"App\Entity\Entreprise" given
Here is the code of the stagiaire entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\StagiaireRepository")
*/
class Stagiaire
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="civilite", type="string", length=3, nullable=false)
*/
private $civilite = 'Mr';
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=24, nullable=false)
*/
private $nom;
/**
* #var string
*
* #ORM\Column(name="prenom", type="string", length=16, nullable=false)
*/
private $prenom;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Entreprise")
* #ORM\JoinColumn(nullable=false)
*/
private $entreprise;
/**
* #var string
*
* #ORM\Column(name="status", type="string", length=12, nullable=false)
*/
private $status;
public function __construct()
{
//$this->date = new \Datetime();
//$this->entreprise = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getCivilite(): ?string
{
return $this->civilite;
}
public function setCivilite(string $civilite): self
{
$this->civilite = $civilite;
return $this;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getPrenom(): ?string
{
return $this->prenom;
}
public function setPrenom(string $prenom): self
{
$this->prenom = $prenom;
return $this;
}
public function getEntreprise(): ?int
{
return $this->entreprise;
}
public function setEntreprise(int $entreprise): self
{
$this->entreprise = $entreprise;
return $this;
}
public function getStatus(): ?string
{
return $this->status;
}
public function setStatus(string $status): self
{
$this->status = $status;
return $this;
}
Here is the code of the form:
use App\Entity\Stagiaire;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class StagiaireType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choicesCivil = [
'Mr' => '1',
'Mme' => '2'
];
$choicesStatus = [
'Gérant' => '1',
'Salarié' => '2'
];
$builder
->add('civilite', ChoiceType::class, [
'data' => '1', // cochée par défaut
'choices' => $choicesCivil,
'expanded' => true, // => boutons
'label' => 'Civilité',
'multiple' => false
])
->add('nom')
->add('prenom')
->add('entreprise', EntityType::class, array(
'class' => 'App:Entreprise',
'placeholder' => 'Choisir une entreprise',
'choice_label' => 'enseigne',
))
->add('status', ChoiceType::class, [
'data' => '1', // cochée par défaut
'choices' => $choicesStatus,
'expanded' => true, // => boutons
'label' => 'Statut',
'multiple' => false
])
->add('create', SubmitType::class, ['label' => 'Enregistrer', 'attr' => ['class' => 'btn btn-primary']]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Stagiaire::class,
]);
}
}
And here is the controller:
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Entity\Stagiaire;
use App\Entity\Entreprise;
use App\Repository\StagiaireRepository;
use App\Form\StagiaireType;
class StagiaireController extends Controller
{
public function fStagiaire($id,$cat,$action, Request $request)
{
if ($action=='insert') {
$stagiaire = new Stagiaire();
}
elseif($action=='update' || $action=='delete') {
$stagiaire = $this->getDoctrine()
->getManager()
->getRepository('App:Stagiaire')
->find($id)
;
}
else { return;}
$form = $this->get('form.factory')->create(StagiaireType::Class, $stagiaire);
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {...
The error occurs at the line $form->handleRequest($request);
I am searching for a few days but I do not found the solution.
Has anybody an idea to help?
Your function setEntreprise(int $entreprise): self is being given an object (an App\Entity\Entreprise), which is not what it is expecting. While the record in the database will be an int (the primary key id on the Entreprise entity - and in fact, it will be a separate database table that refers back to this record's ID with zero or more Entreprise records), it is made available to your code (from getEntreprise()) as the object (so returning the nullable int there would also fail when you come to read the linked entity out - it should be an empty ArrayCollection()).
Your setter & getter need to deal with the objects - and since it's a 'ManyToOne' relationship, there can more more than one. You will likely also want an addEntreprise() function to be able to add more objects. The ORM will deal with saving the data in them to the database, and linking them back in later when this record is read in.
public function __construct()
{
//$this->date = new \Datetime();
//$this->entreprise = new ArrayCollection();
}
# These lines say that `$this->entreprise` is an array - by default empty, but
# it can be filled up with a number of linked objects.
You need to change your functions to accept an entreprise object instead of an id:
public function getEntreprise(): ?Entreprise
{
return $this->entreprise;
}
public function setEntreprise(Entreprise $entreprise): self
{
$this->entreprise = $entreprise;
return $this;
}
and you have to add use App\Entity\Entreprise; to the Stagiaire class.
I try to insert records with relations OneToMany-ManyToOne, but I got error.
Expected argument of type "string", "App\Entity\Question" given.
I have next entities question and answer.
class Question
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="text")
*/
private $title;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Answer", mappedBy="question", orphanRemoval=true)
*/
private $answers;
public function __construct()
{
$this->answers = new ArrayCollection();
}
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 Collection|Answer[]
*/
public function getAnswers(): Collection
{
return $this->answers;
}
public function addAnswer(Answer $answer): self
{
if (!$this->answers->contains($answer)) {
$this->answers[] = $answer;
$answer->setQuestion($this);
}
return $this;
}
public function removeAnswer(Answer $answer): self
{
if ($this->answers->contains($answer)) {
$this->answers->removeElement($answer);
if ($answer->getQuestion() === $this) {
$answer->setQuestion(null);
}
}
return $this;
}
}
Entity Answer
class Answer
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="text")
*/
private $text;
/**
* #ORM\Column(type="boolean")
*/
private $is_correct;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Question", inversedBy="answers")
* #ORM\JoinColumn(nullable=false)
*/
private $question;
public function getId(): ?int
{
return $this->id;
}
public function getText(): ?string
{
return $this->text;
}
public function setText(string $text): self
{
$this->text = $text;
return $this;
}
public function getIsCorrect(): ?bool
{
return $this->is_correct;
}
public function setIsCorrect(bool $is_correct): self
{
$this->is_correct = $is_correct;
return $this;
}
public function getQuestion(): ?question
{
return $this->question;
}
public function setQuestion(?Question $question): self
{
$this->question = $question;
return $this;
}
}
My form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', EntityType::class, array(
'class' => Question::class,
'choice_label' => 'title',
'label' => 'Question'
));
$builder
->add('answers', CollectionType::class, array(
'entry_type' => AnswerType::class,
'entry_options' => array('label' => false),
'allow_add' => true,
'by_reference' => false,
));
$builder
->add('create', SubmitType::class, ['label' => 'Add', 'attr' => ['class' => 'btn btn-primary']]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Question::class
]);
}
My fragment of controller
$question = new Question();
$answer = new Answer();
$question->addAnswer($answer);
$form1 = $this->createForm(QuestionAnswerType::class, $question);
$form1->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($question);
$em->flush();
}
The error pointer at next line
$form1->handleRequest($request);
I know I have problem with my controller, but I don't know how resolve it.
I don't get it how right insert records with relations OneToMany-ManyToOne. Could you help me?
I think the reason that you are seeing this error is due to the fact that in your Question class you have defined the title field as a Text type (#ORM\Column(type="text")).
However in your Form you have defined the form field title as an EntityType this is the reason I think why you are seeing this error.
You can fix this by changing the database mapping of the title field in your Question class or you could change your Form to use a TextType instead of an EntityType
Hope this helps
You have to do changes into two places.
1) First change into "Question" class
/**
* #ORM\Column(type="string")
*/
private $title;
2) Second into your form class replace "EntityType::class" with "TextType::class" and remove 'class' and 'choice_label' attribute from title
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, array(
'label' => 'Question'
));
..... your other code ...
}
In class Question __toString function is missing
public function __toString()
{
return $this->property-to-show;
}
I have a form built on the idea of a Purchase. It has Purchase Items, a Payment Amount and a Payment Type (VISA, Mastercard, Cash). I have the form preloaded with 2 Purchase Items however I am trying to add an additional Purchase Item if the user chooses a Card Payment Type (VISA or Mastercard) Type.
This additional Purchase Item I am trying to add via the Controller.
Rendered Form view
The question is really, where do I implement this functionality in the Controller Action... Or is it better as an EventListener on the Form Type?
When the form is submitted with the additional Card Fee Purchase Item I get the following error...
Catchable Fatal Error: Argument 1 passed to Aazp\BookingBundle\Entity\PurchaseItem::__construct() must be an instance of Aazp\BookingBundle\Entity\Product, none given, called in /project/vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/Type/FormType.php on line 141 and defined in /project/src/Aazp/BookingBundle/Entity/PurchaseItem.php line 20
The BookingController
namespace Aazp\BookingBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Aazp\BookingBundle\Entity\Booking;
use Aazp\BookingBundle\Entity\Passenger;
use Aazp\BookingBundle\Entity\Payment;
use Aazp\BookingBundle\Entity\Purchase;
use Aazp\BookingBundle\Entity\PurchaseItem;
use Aazp\BookingBundle\Form\PurchaseItemType;
use Aazp\BookingBundle\Form\PurchaseType;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class BookingController extends Controller
{
public function passengerPaymentAction($passenger_id)
{
$request = Request::createFromGlobals();
$purchaseBalance = 0.0;
//Query the selected Passenger
$em = $this->getDoctrine()->getManager();
$passenger = $request->attributes->get('passenger', $em->getRepository('AazpBookingBundle:Passenger')->find($passenger_id));
if (!$passenger) {
throw $this->createNotFoundException('Unable to find Passenger entity.');
}
$purchase = $passenger->getPurchase();
//Has this Passenger made a Payment. If yes then Purchase exists.
if($purchase === NULL) //If Purchase does not exist then preload the form with default products.
{
$purchase = new Purchase();
$product_category_photo = $em->getRepository('AazpBookingBundle:ProductCategory')->findOneByName('FLIGHT-PHOTO');
$product_photo_option = $em->getRepository('AazpBookingBundle:Product')->findOneByProductCategory($product_category_photo);
if (!$product_photo_option) {
throw $this->createNotFoundException('Unable to find Flight Photo Product entity.');
}
$purchase_item_flight = new PurchaseItem($passenger->getFlight());
$purchase_item_photo_option = new PurchaseItem($product_photo_option);
//Add Purchase Items to the Purchase
$purchase->addPurchaseItem($purchase_item_flight);
$purchase->addPurchaseItem($purchase_item_photo_option);
//Set the Purchase on the Passenger
$passenger->setPurchase($purchase);
}
//Ajax call triggered by onChange event on PaymentType radio button in form
//Add additional Purchase Item for Card Type Payment
if($form->get('paymentType')->getData()->getId() > 1)
{
//PaymentType selected/modified then calculate Payment Fee
$product_category_card_fee = $em->getRepository('AazpBookingBundle:ProductCategory')->findOneByName('CARD-FEE');
$product_card_fee = $em->getRepository('AazpBookingBundle:Product')->findOneByProductCategory($product_category_card_fee);
if (!$productcard_fee) {
throw $this->createNotFoundException('Unable to find Card Fee Product entity.');
}
$purchase_item_card_fee = new PurchaseItem($product_card_fee);
//Add Purchase Items to the Purchase
$purchase->addPurchaseItem($purchase_item_card_fee);
$passenger->setPurchase($purchase);
return $this->render('AazpBookingBundle:Purchase:summary.html.twig', array(
'passenger' => $passenger,
'form' => $form->createView(),
));
}
$form = $this->createForm(new PurchaseType($em), $purchase);
$form->handleRequest($request);
//If form is Valid create Payment and persist.
if ($form->isValid())
{
$payment = new Payment();
$payment->setAmount($form->get('paymentAmount')->getData());
$payment->setPaymentType($form->get('paymentType')->getData());
$payment->setDescription($form->get('description')->getData());
$passenger->getPurchase()->addPayment($payment);
$passenger->getBooking()->setStatus(Booking::STATUS_CONFIRMED);
$em->persist($passenger->getPurchase());
$em->flush();
$this->get('session')->getFlashBag()->add('message', 'Payment '.$payment->getAmount().' CHF has been successful!');
return $this->redirect($this->generateUrl('booking_show', array ('id'=> $passenger->getBooking()->getId())));
}
return $this->render('AazpBookingBundle:Purchase:summary.html.twig', array(
'passenger' => $passenger,
'form' => $form->createView(),
));
}
}
The PurchaseItemType
namespace Aazp\BookingBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Aazp\BookingBundle\Entity\ProductRepository;
class PurchaseItemType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('product', 'entity', array('label' => 'Flight', 'class' => 'AazpBookingBundle:Product','property' => 'name', 'empty_value' => 'Please Select', 'required' => false, ));
$builder->add('amount', 'number', array('precision' => 2));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Aazp\BookingBundle\Entity\PurchaseItem', ));
}
public function getName()
{
return 'purchaseItem';
}
}
The PurchaseType
namespace Aazp\BookingBundle\Form;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Aazp\BookingBundle\Entity\PurchaseItem;
class PurchaseType extends AbstractType
{
protected $em;
function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('purchaseItems', 'collection', array('type' => new PurchaseItemType(), 'allow_add' => true, 'allow_delete' => true, 'by_reference' => false));
$builder->add('paymentType', 'entity', array('label' => 'Payment Type', 'class' => 'AazpBookingBundle:PaymentType','property' => 'name', 'mapped' => false, 'expanded' => true));
$builder->add('paymentAmount', 'number', array('precision' => 2, 'data' => 0.0, 'mapped' => false));
$builder->add('description', 'text', array('mapped' => false, 'required' => false));
$builder->add('cancel', 'submit', array('attr' => array('formnovalidate' => true, 'data-toggle' => 'modal', 'data-target' => '#cancelWarning', )));
$builder->add('pay', 'submit');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Aazp\BookingBundle\Entity\Purchase', ));
}
public function getName()
{
return 'purchase';
}
}
The Purchase Entity
<?php
namespace Aazp\BookingBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
use Aazp\MainBundle\Entity\BaseEntity;
/**
* #ORM\Entity(repositoryClass="Aazp\BookingBundle\Entity\PurchaseRepository")
* #ORM\Table(name="purchase")
* #Gedmo\SoftDeleteable(fieldName="deleted")
*/
class Purchase extends BaseEntity
{
/**
* Constructor
*/
public function __construct()
{
$this->purchaseItems = new \Doctrine\Common\Collections\ArrayCollection();
$this->payments = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="PurchaseItem", cascade={"all"})
* #ORM\JoinTable(name="purchase_purchase_items",
* joinColumns={#ORM\JoinColumn(name="purchase_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="purchase_item_id", referencedColumnName="id", unique=true)}
* )
**/
protected $purchaseItems;
/**
* #ORM\ManyToMany(targetEntity="Payment", inversedBy="purchases", cascade={"all"})
* #ORM\JoinTable(name="purchases_payments",
* joinColumns={#ORM\JoinColumn(name="purchase_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="payment_id", referencedColumnName="id")}
* )
**/
protected $payments;
/**
* #ORM\OneToOne(targetEntity="Passenger", mappedBy="purchase")
**/
protected $passenger;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add purchaseItems
*
* #param \Aazp\BookingBundle\Entity\PurchaseItem $purchaseItems
* #return Purchase
*/
public function addPurchaseItem(\Aazp\BookingBundle\Entity\PurchaseItem $purchaseItems)
{
$this->purchaseItems[] = $purchaseItems;
return $this;
}
/**
* Remove purchaseItems
*
* #param \Aazp\BookingBundle\Entity\PurchaseItem $purchaseItems
*/
public function removePurchaseItem(\Aazp\BookingBundle\Entity\PurchaseItem $purchaseItems)
{
$this->purchaseItems->removeElement($purchaseItems);
}
/**
* Get purchaseItems
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPurchaseItems()
{
return $this->purchaseItems;
}
/**
* Add payments
*
* #param \Aazp\BookingBundle\Entity\Payment $payments
* #return Purchase
*/
public function addPayment(\Aazp\BookingBundle\Entity\Payment $payments)
{
$this->payments[] = $payments;
return $this;
}
/**
* Remove payments
*
* #param \Aazp\BookingBundle\Entity\Payment $payments
*/
public function removePayment(\Aazp\BookingBundle\Entity\Payment $payments)
{
$this->payments->removeElement($payments);
}
/**
* Get payments
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPayments()
{
return $this->payments;
}
/**
* Set passenger
*
* #param \Aazp\BookingBundle\Entity\Passenger $passenger
* #return Purchase
*/
public function setPassenger(\Aazp\BookingBundle\Entity\Passenger $passenger = null)
{
$this->passenger = $passenger;
return $this;
}
/**
* Get passenger
*
* #return \Aazp\BookingBundle\Entity\Passenger
*/
public function getPassenger()
{
return $this->passenger;
}
}
This happened because the default data_class option calls that constructor with no arguments. If your class Aazp\Booking Bundle\Entity\PurchaseItem has parameters in the constructor, you need to use the empty_data option to instantiate:
// Aazp\BookingBundle\Form\PurchaseItemType.php
$resolver->setDefaults([
'empty_data' => function (FormInterface $form) {
return new PurchaseItem($form->get('product')->getData());
},
]);
You can lean more about this option here.
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]
}
}
I have a Symfony 2 application, with a form that needs to store a reference to another entity (project) in a hidden field. The project entity is passed in via the form options, my plan was to have a field of the type 'hidden' that simply contains the entity id, this should then be transformed into a project entity on when the form is submitted.
I'm going about this by using a model transformer to transform between the entity to a string (it's ID). However when I try to view the form, I get the following error:
The form's view data is expected to be an instance of class Foo\BarBundle\Entity\Project, 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 Foo\BarBundle\Entity\Project.
Here is my form class:
<?php
namespace Foo\BarBundle\Form\SED\Waste;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Foo\BarBundle\Form\DataTransformer\EntityToEntityIdTransformer;
/**
* Class WasteContractorEntryType
* #package Foo\BarBundle\Form\CommunityInvestment\Base
*/
class WasteContractorEntryType extends AbstractType
{
protected $name;
protected $type;
protected $phase;
protected $wasteComponent;
public function __construct($formName, $type, $phase, $wasteComponent)
{
$this->name = $formName;
$this->type = $type;
$this->phase = $phase;
$this->wasteComponent = $wasteComponent;
}
/**
* #return mixed
*/
public function getType()
{
return $this->type;
}
/**
* #return mixed
*/
public function getPhase()
{
return $this->phase;
}
/**
* #return mixed
*/
public function getProject()
{
return $this->project;
}
/**
* #return mixed
*/
public function getWasteComponent()
{
return $this->wasteComponent;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$em = $options['em'];
$wasteComponentTransformer = new EntityToEntityIdTransformer($em,
'Foo\BarBundle\Entity\SED\Waste\WasteComponent');
$projectTransformer = new EntityToEntityIdTransformer($em, 'Foo\BarBundle\Entity\Project');
$builder->add('id', 'hidden');
$builder->add(
$builder->create('project', 'hidden', array(
'data' => $options['project'],
'by_reference' => false
))
->addModelTransformer($projectTransformer)
);
$builder->add(
$builder->create('wasteComponent', 'hidden', array(
'data' => $this->getWasteComponent()
))
->addModelTransformer($wasteComponentTransformer)
);
$builder->add('phase', 'hidden', array(
'data' => $this->getPhase()
));
$builder->add('type', 'hidden', array(
'data' => $this->getType()
));
$builder->add('percentDivertedFromLandfill', 'text', array());
$builder->add('wasteContractor', 'entity', array(
'class' => 'Foo\BazBundle\Entity\Contractor',
'property' => 'name',
'attr' => array(
'class' => 'js-select2'
)
));
}
public function getName()
{
return $this->name;
}
/**
* {#inheritDoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => true,
'data_class' => 'Foo\BarBundle\Entity\SED\Waste\WasteContractorEntry'
))
->setRequired(array(
'em',
'project'
))
->setAllowedTypes(array(
'em' => 'Doctrine\Common\Persistence\ObjectManager',
'project' => 'Foo\BarBundle\Entity\Project'
));
}
}
And my model transformer class:
<?php
namespace Foo\BarBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
class EntityToEntityIdTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $om;
/**
* #var Entity class
*/
protected $entityClass;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om, $className)
{
$this->om = $om;
$this->entityClass = $className;
}
protected function getEntityClass()
{
return $this->entityClass;
}
/**
* Transforms an object (project) to a string (id).
*
* #param Project|null $issue
* #return string
*/
public function transform($entity)
{
if (null === $entity) {
return "";
}
return $entity->getId();
}
/**
* Transforms a string (id) to an object (project).
*
* #param string $id
*
* #return Issue|null
*
* #throws TransformationFailedException if object (project) is not found.
*/
public function reverseTransform($id)
{
if (!$id) {
return null;
}
$entity = $this->om
->getRepository($this->getEntityClass())
->find($id);
if (null === $entity) {
throw new TransformationFailedException(sprintf(
'An entity of class %s with id "%s" does not exist!',
$this->getEntityClass(),
$id
));
}
return $entity;
}
}
I've tried using a adding the transformer as a view transformer instead of a model transformer, however then I just get a slightly different error:
The form's view data is expected to be an instance of class
Foo\BarBundle\Entity\Project, but is a(n) integer. You can avoid this
error by setting the "data_class" option to null or by adding a view
transformer that transforms a(n) integer to an instance of
Foo\BarBundle\Entity\Project.
It seems that setting 'data_class' to null as suggested by the exception message above is the solution. I had previously rejected this as it seems counter-intuitive when we know that the purpose of the field is to reference a project entity.
With the 'data_class' option set to null, the hidden project field contains the project id, and upon submission, calling getProject() on the created entity returns the correct project object.