Symfony 5.0.8 weird issue when updating a simple entity - symfony

Here is my entity:
<?php
namespace App\Entity\Contact;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="contact_contact")
*/
class Contact
{
/**
* #ORM\Id
* #ORM\Column(type="integer", options={"unsigned":true})
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #Assert\NotBlank
* #ORM\Column(type="string", length=40, nullable=true)
*/
private $fname;
/**
* #ORM\Column(type="string", length=40, nullable=true)
*/
private $lname;
public function getId(): ?int
{
return $this->id;
}
public function getFname(): ?string
{
return $this->fname;
}
public function setFname(string $fname): self
{
$this->fname = $fname;
return $this;
}
public function getLname(): ?string
{
return $this->lname;
}
public function setLname(?string $lname): self
{
$this->lname = $lname;
return $this;
}
}
Here is the edit controller action code:
/**
* #Route("/{id}/edit", name="contact_contact_edit", methods={"GET","POST"})
*/
public function edit(Request $request, Contact $contact): Response
{
$form = $this->createForm(ContactType::class, $contact);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$this->getDoctrine()->getManager()->flush();
}
}
return $this->render('contact/contact/edit.html.twig', [
'contact' => $contact,
'form' => $form->createView(),
]);
}
When I post the form but leave the fname (first name) field empty...I get this error (Symfony\Component\PropertyAccess\Exception\InvalidArgumentException)
Expected argument of type "string", "null" given at property path
"fname".
When creating the entity, the #Assert works as expected and the message says so...but if I leave it blank and update post...bzzzt error.
What am I missing?
EDIT | Here is the form class incase thats doing something?
<?php
namespace App\Form\Contact;
use App\Entity\Contact\Contact;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fname', TextType::class, ['label' => 'First Name'])
->add('lname', TextType::class, ['label' => 'Last Name']);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Contact::class,
'min_entry' => false,
// NOTE: Must be added to every form class to disable HTML5 validation
'attr' => ['novalidate' => 'novalidate']
]);
$resolver->setAllowedTypes('min_entry', 'bool');
}
}

That's one of the reasons you should avoid allowing the form component changing your entities directly. It will set the data, and then validate it. So it's totally possible for the entity to be in an invalid state after the form has been submitted.
Anyway, you can specify what an empty value should be:
->add('fname', TextType::class, ['label' => 'First Name', 'empty_data' => ''])

Related

Class does not implement “Symfony\Component\Form\FormTypeInterface”

I create a form to modify my data, but when I want to access my form, I get this error :
Could not load type "App\Entity\Clients": class does not implement
"Symfony\Component\Form\FormTypeInterface".
ClientsEntity :
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ClientsRepository")
* #ORM\Table(name="app_clients")
*/
class Clients
{
/**
* #var int
*
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var string
*
* * #ORM\Column(type="string", length=180, unique=true)
*/
private $name;
public function getId(): ?int
{
return $this->id;
}
public function setName(string $name): void
{
$this->name = $name;
}
public function getName(): ?string
{
return $this->name;
}
public function __toString(): string
{
return $this->name;
}
}
ClientType :
<?php
namespace App\Form;
use App\Entity\Clients;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ClientType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', null, array(
'label' => 'Domaine du client',
'required' => true,
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Clients::class,
'name' => null
]);
}
}
ClientsController :
/**
* #Route("/edit/{id}", name="support_admin_client_edit", methods={"GET","POST"})
*/
public function edit(Request $request, Clients $clients): Response
{
$form = $this->createForm(ClientType::class, $clients);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('support_admin_client_index', [
'id' => $clients->getId(),
]);
}
return $this->render('admin/ticketing/client/edit.html.twig', [
'clients' => $clients,
'form' => $form->createView(),
]);
}
My form implements AbstractType so I do not see where my error comes from.
Do you know where this message comes from?

Expected argument of type "integer", "App\Entity\Entreprise" given

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.

Add additional items to a Symfony2 Form Collection type after page submit

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.

How to access one to one embedded form symfony2

I used symfony 2 only few days.
I got two entities and i want to create one form and save data from this form into database.
UserDetails and Contract have relations OneToOne.
I embed a form of Contract into UserDetails form (form apper on web) but when i set some data into form and click button "save" i get a error. as i notice a "try" to assign an array instead of Contract i don't know how to access this new contract entite in Controler.
example ERRORS:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Leave\DatabaseBundle\Entity\UserDetails::setContract() must be an instance of Leave\DatabaseBundle\Entity\Contract, array given, called in /var/www/nowyUrlop/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php on line 360 and defined in /var/www/nowyUrlop/src/Leave/DatabaseBundle/Entity/UserDetails.php line 165
at UserDetails->setContract(array('start_contr' => object(DateTime), 'end_contr' => object(DateTime), 'hours_per_week' => '6')) in /var/www/nowyUrlop/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php line 360
UserDetails entity:
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Annotations\AnnotationReader;
use Leave\DatabaseBundle\Entity\User;
/**
* #ORM\Entity
* #ORM\Table(name="userDetails")
*/
class UserDetails {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Contract", mappedBy="user_details", cascade={"persist"})
*/
protected $contract;
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="userDetails")
* #ORM\JoinColumn(name="user_id", nullable = true, referencedColumnName="id")
* */
protected $user;
Contract Entity:
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="contract")
*/
class Contract {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="date")
*/
protected $start_contr;
/**
* #ORM\Column(type="date")
*/
protected $end_contr;
/**
* #ORM\Column(type="integer")
*/
protected $type ;
/**
* #ORM\Column(type="integer")
*/
protected $hours_per_week;
/**
* #ORM\OneToOne(targetEntity="UserDetails", inversedBy="contract")
* #ORM\JoinColumn(name="user_details_id", nullable = true, referencedColumnName="id")
* */
protected $user_details;
public function setUserDetails(\Leave\DatabaseBundle\Entity\UserDetails $userDetails = null)
{
$this->user_details = $userDetails;
return $this;
}
/**
* Get user_details
*
* #return \Leave\DatabaseBundle\Entity\UserDetails
*/
public function getUserDetails()
{
return $this->user_details;
}
UserDetals Form:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Leave\DatabaseBundle\Form\Type\ContractFormType;
class UserDetailsFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('exp', 'text')
->add('total_leave', 'text')
->add('days_left', 'text')
->add('contract', new contractFormType())
->add('save', 'submit');
}
public function getName()
{
return 'userDetails';
}
}
Contract Form:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ContractFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('start_contr', 'date', array('widget' => 'single_text'))
->add('end_contr', 'date', array('widget' => 'single_text'))
->add('hours_per_week', 'text');
}
public function getName()
{
return 'contractForm';
}
}
Controller:
public function editUserAction(Request $request) {
$user = $this->get('security.context')->getToken()->getUser();
$userDetails = new UserDetails();
$form = $this->createForm(new UserDetailsFormType(), $userDetails);
$userDetails->setUser($user);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($userDetails);
$em->flush();
return $this->render('LeaveEmployeeBundle:Employee:editUser.html.twig', array(
'formEditUser' => $form->createView(),
'userDetails' => $userDetails,
'user' => $user
));
}
return $this->render('LeaveEmployeeBundle:Employee:editUser.html.twig', array(
'formEditUser' => $form->createView()
));
}
You have to pass data_class as second parameter.
In your UserDetals Form: do the following
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Leave\DatabaseBundle\Form\Type\ContractFormType;
class UserDetailsFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('exp', 'text')
->add('total_leave', 'text')
->add('days_left', 'text')
->add('contract', new contractFormType(), array(
'data_class' => 'Leave\DatabaseBundle\Entity\Contract')
)
->add('save', 'submit');
}
public function getName()
{
return 'userDetails';
}
}

Symfony2 and Doctrine - 'Catchable fatal error' on flush

Names changed due to NDA.
I'm trying to come up with a survey form. Each survey question can have multiple answers/scores, so there's a natural 1:* relationship to them. That said, for the public-facing form, I need to have a 1:1 relationship between the score and the question it relates to, which is what I'm working on now. Right now, the survey is open to the public, so each completed survey is not related to a user.
The interesting parts of my current setup are as follows...
Question:
namespace Acme\MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
class Question
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string question
*
* #ORM\Column(name="question", type="string", length=255)
*/
private $question;
/**
* #var ArrayCollection scores
*
* #ORM\OneToMany(targetEntity="Score", mappedBy="question")
*/
private $scores;
public function __construct()
{
$this->scores = new ArrayCollection();
}
// other getters and setters
/**
* #param $score
*/
public function setScore($score)
{
$this->scores->add($score);
}
/**
* #return mixed
*/
public function getScore()
{
if (get_class($this->scores) === 'ArrayCollection') {
return $this->scores->current();
} else {
return $this->scores;
}
}
}
Those last two are helper methods so I can add/retrieve individual scores. The type checking convolutions were due to an error I encountered here
Score:
namespace Acme\MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
class Score
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var integer $question
*
* #ORM\ManyToOne(targetEntity="Question", inversedBy="scores")
* #ORM\JoinColumn(name="question_id", referencedColumnName="id")
*/
private $question;
/**
* #var float score
*
* #ORM\Column(name="score", type="float")
*/
private $score;
// getters and setters
}
Controller method:
public function takeSurveyAction(Request $request)
{
$em = $this->get('doctrine')->getManager();
$questions = $em->getRepository('Acme\MyBundle\Entity\Question')->findAll();
$viewQuestions = array();
foreach ($questions as $question) {
$viewQuestions[] = $question;
$rating = new Score();
$rating->setQuestion($question->getId());
$question->setRatings($rating);
}
$form = $this->createForm(new SurveyType(), array('questions' => $questions));
if ('POST' === $request->getMethod()) {
$form->bind($request);
if ($form->isValid()) {
foreach ($questions as $q) {
$em->persist($q);
}
$em->flush();
$em->clear();
$url = $this->get('router')->generate('_main');
$response = new RedirectResponse($url);
return $response;
}
}
return $this->render('MyBundle:Survey:take.html.twig', array('form' => $form->createView(), 'questions' => $viewQuestions));
}
My form types....
SurveyType:
namespace Acme\MyBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SurveyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('questions', 'collection', array('type' => new SurveyListItemType()));
}
public function getName()
{
return 'survey';
}
}
SurveyListItemType:
namespace Acme\MyBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SurveyListItemType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('rating', new SurveyScoreType());
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Acme\MyBundle\Entity\Question'));
}
public function getName()
{
return 'survey_list_item_type';
}
}
SurveyScoreType:
namespace Acme\MyBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SurveyRatingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('score', 'choice', array('choices' => array(
'0' => '',
'0.5' => '',
'1' => '',
'1.5' => '',
'2' => '',
'2.5' => '',
'3' => '',
'3.5' => '',
'4' => '',
'4.5' => '',
'5' => ''
), 'expanded' => true, 'multiple' => false));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Acme\MyBundle\Entity\Score'));
}
public function getName()
{
return 'survey_score_type';
}
}
Okay, with all of that, I'm getting the following error when Doctrine's EntityManager attempts to flush() in my controller action:
Catchable Fatal Error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given, called in /home/kevin/www/project/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 547 and defined in /home/kevin/www/project/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php line 47
I believe it has to do with the questions' related scores, as they're supposed to be an array(collection) in Question, but they're individual instances in this case. The only problem is I'm not sure how to fix it.
I'm thinking my form setup may be too complex. All I really need to do is attach each Question.id to each related Score. I'm just not sure the best way to build the form part of it so everything is persisted properly.
I believe your error is here
$rating = new Score();
//...
$question->setRatings($rating);
Usually if you have an ArrayCollection in your Entity, then you have addChildEntity and removeChildEntity methods that add and remove elements from the ArrayCollection.
setRatings() would take an array of entities, rather than a single entity.
Assuming that you do have this method, try
$question->addRating($rating);
I think you have a mistake in your setRating method.
You have
$this->score->add($score);
It should be:
$this->scores->add($score);
I was able to solve it by simply handling the Scores. So, with that approach, I was able to remove SurveyListItemType, and make the following changes:
SurveyType:
namespace Acme\MyBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SurveyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('scores', 'collection', array('type' => new SurveyRatingType()));
}
public function getName()
{
return 'survey';
}
}
Note how the collection type is now mapped to SurveyRatingType.
SurveyRatingType:
namespace Acme\MyBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SurveyRatingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('score', 'choice', array('choices' => array(
'0' => '',
'0.5' => '',
'1' => '',
'1.5' => '',
'2' => '',
'2.5' => '',
'3' => '',
'3.5' => '',
'4' => '',
'4.5' => '',
'5' => ''
), 'expanded' => true, 'multiple' => false));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Acme\MyBundle\Entity\Score'));
}
public function getName()
{
return 'survey_rating_type';
}
}
And my modified controller action:
public function takeSurveyAction(Request $request)
{
$em = $this->get('doctrine')->getManager();
$questions = $em->getRepository('Acme\MyBundle\Entity\Question')->findAll();
$ratings = array();
foreach ($questions as $question) {
$rating = new SurveyRating();
$rating->setQuestion($question);
$ratings[] = $rating;
}
$form = $this->createForm(new SurveyType(), array('ratings' => $ratings));
if ('POST' === $request->getMethod()) {
$form->bind($request);
if ($form->isValid()) {
foreach ($ratings as $r) {
$em->persist($r);
}
$em->flush();
$em->clear();
$url = $this->get('router')->generate('_main');
$response = new RedirectResponse($url);
return $response;
}
}
return $this->render('MyBundle:Survey:take.html.twig', array('form' => $form->createView(), 'questions' => $questions));
}
I had a feeling I was doing it wrong due to the three form types. That really jumped out as a bad code smell. Thanks to everyone for their patience and attempts at helping. :)

Resources