Symfony 2 - Multiple form fields for one database row - symfony

I'm using Symfony 2.1 and Doctrine 2.
I'm dealing with 2 main entities : Place and Feature, with a ManyToMany relationship between them.
There's many features in the database, and to group them by theme the Feature is also related to a FeatureCategory entity with a ManyToOne relationship.
Here's the code of the different entities :
The Place entity
namespace Mv\PlaceBundle\Entity;
…
/**
* Mv\PlaceBundle\Entity\Place
*
* #ORM\Table(name="place")
* #ORM\Entity(repositoryClass="Mv\PlaceBundle\Entity\Repository\PlaceRepository")
* #ORM\HasLifecycleCallbacks
*/
class Place
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
* #Assert\NotBlank
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="\Mv\MainBundle\Entity\Feature")
* #ORM\JoinTable(name="places_features",
* joinColumns={#ORM\JoinColumn(name="place_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="feature_id", referencedColumnName="id")}
* )
*/
private $features;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Place
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add features
*
* #param \Mv\MainBundle\Entity\Feature $features
* #return Place
*/
public function addFeature(\Mv\MainBundle\Entity\Feature $features)
{
$this->features[] = $features;
echo 'Add "'.$features.'" - Total '.count($this->features).'<br />';
return $this;
}
/**
* Remove features
*
* #param \Mv\MainBundle\Entity\Feature $features
*/
public function removeFeature(\Mv\MainBundle\Entity\Feature $features)
{
$this->features->removeElement($features);
}
/**
* Get features
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFeatures()
{
return $this->features;
}
public function __construct()
{
$this->features = new \Doctrine\Common\Collections\ArrayCollection();
}
The Feature Entity :
namespace Mv\MainBundle\Entity;
…
/**
* #ORM\Entity
* #ORM\Table(name="feature")
* #ORM\HasLifecycleCallbacks
*/
class Feature
{
use KrToolsTraits\PictureTrait;
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="label", type="string", length=255)
* #Assert\NotBlank()
*/
protected $label;
/**
* #ORM\ManyToOne(targetEntity="\Mv\MainBundle\Entity\FeatureCategory", inversedBy="features", cascade={"persist"})
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set label
*
* #param string $label
* #return Feature
*/
public function setLabel($label)
{
$this->label = $label;
return $this;
}
/**
* Get label
*
* #return string
*/
public function getLabel()
{
return $this->label;
}
/**
* Set category
*
* #param Mv\MainBundle\Entity\FeatureCategory $category
* #return Feature
*/
public function setCategory(\Mv\MainBundle\Entity\FeatureCategory $category = null)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* #return Mv\MainBundle\Entity\FeatureCategory
*/
public function getCategory()
{
return $this->category;
}
}
The FeatureCategory entity :
namespace Mv\MainBundle\Entity;
...
/**
* #ORM\Entity
* #ORM\Table(name="feature_category")
*/
class FeatureCategory
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="code", type="string", length=255)
* #Assert\NotBlank()
*/
protected $code;
/**
* #ORM\Column(name="label", type="string", length=255)
* #Assert\NotBlank()
*/
protected $label;
/**
* #ORM\OneToMany(targetEntity="\Mv\MainBundle\Entity\Feature", mappedBy="category", cascade={"persist", "remove"}, orphanRemoval=true)
* #Assert\Valid()
*/
private $features;
public function __construct()
{
$this->features = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set code
*
* #param string $code
* #return Feature
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* Get code
*
* #return string
*/
public function getCode()
{
return $this->code;
}
/**
* Set label
*
* #param string $label
* #return Feature
*/
public function setLabel($label)
{
$this->label = $label;
return $this;
}
/**
* Get label
*
* #return string
*/
public function getLabel()
{
return $this->label;
}
/**
* Add features
*
* #param \Mv\MainBundle\Entity\Feature $features
*/
public function addFeatures(\Mv\MainBundle\Entity\Feature $features){
$features->setCategory($this);
$this->features[] = $features;
}
/**
* Get features
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFeatures()
{
return $this->features;
}
/*
* Set features
*/
public function setFeatures(\Doctrine\Common\Collections\Collection $features)
{
foreach ($features as $feature)
{
$feature->setCategory($this);
}
$this->features = $features;
}
/**
* Remove features
*
* #param Mv\MainBundle\Entity\Feature $features
*/
public function removeFeature(\Mv\MainBundle\Entity\Feature $features)
{
$this->features->removeElement($features);
}
/**
* Add features
*
* #param Mv\MainBundle\Entity\Feature $features
* #return FeatureCategory
*/
public function addFeature(\Mv\MainBundle\Entity\Feature $features)
{
$features->setCategory($this);
$this->features[] = $features;
}
}
Feature table is already populated, and users won't be able to add features but only to select them in a form collection to link them to the Place.
(The Feature entity is for the moment only linked to Places but will be later related to others entities from my application, and will contain all the features available for all entities)
In the Place form I need to display checkboxes of the features available for a Place, but I need to display them grouped by category.
Example :
Visits (FeatureCategory - code VIS) :
Free (Feature)
Paying (Feature)
Languages spoken (FeatureCategory - code LAN) :
English (Feature)
French (Feature)
Spanish (Feature)
My idea
Use virtual forms in my PlaceType form, like this :
$builder
->add('name')
->add('visit', new FeatureType('VIS'), array(
'data_class' => 'Mv\PlaceBundle\Entity\Place'
))
->add('language', new FeatureType('LAN'), array(
'data_class' => 'Mv\PlaceBundle\Entity\Place'
));
And create a FeatureType virtual form, like this :
class FeatureType extends AbstractType
{
protected $codeCat;
public function __construct($codeCat)
{
$this->codeCat = $codeCat;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('features', 'entity', array(
'class' => 'MvMainBundle:Feature',
'query_builder' => function(EntityRepository $er)
{
return $er->createQueryBuilder('f')
->leftJoin('f.category', 'c')
->andWhere('c.code = :codeCat')
->setParameter('codeCat', $this->codeCat)
->orderBy('f.position', 'ASC');
},
'expanded' => true,
'multiple' => true
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'virtual' => true
));
}
public function getName()
{
return 'features';
}
}
With this solution I get what I want but the bind process doesn't persist all the features. Instead of grouping them, it only keeps me and persist the last group "language", and erases all the previouses features datas. To see it in action, if I check the 5 checkboxes, it gets well into the Place->addFeature() function 5 times, but the length of the features arrayCollection is successively : 1, 2, 1, 2, 3.
Any idea on how to do it another way ? If I need to change the model I'm still able to do it.
What is the best way, reusable on my future other entities also related to Feature, to handle this ?
Thank you guys.

I think your original need is only about templating.
So you should not tweak the form and entity persistence logic to get the desired autogenerated form.
You should go back to a basic form
$builder
->add('name')
->add('features', 'entity', array(
'class' => 'MvMainBundle:Feature',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('f')
//order by category.xxx, f.position
},
'expanded' => true,
'multiple' => true
));
And tweak your form.html.twig

Related

error in symfony for manyToOne and form imbricated

I encountered a problem under symfony3 which has blocked me for a while ... I do not understand this error:
Expected value of type "Doctrine\Common\Collections\Collection|array" for association field "AppBundle\Entity\Project#$participants", got "string" instead.
Here is my entity Project:
class Project
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="projects")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Participant", mappedBy="project", cascade={"persist", "remove"})
*/
private $participants;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="content", type="text", nullable=true)
*/
private $content;
/**
* #var string
*
* #ORM\Column(name="place", type="string", length=255, nullable=true)
*/
private $place;
/**
* #var \DateTime
*
* #ORM\Column(name="dateEvent", type="datetime", nullable=true)
*/
private $dateEvent;
/**
* #var boolean
*
* #ORM\Column(name="status", type="boolean")
*/
private $status;
/**
* #var boolean
*
* #ORM\Column(name="in_progress", type="boolean")
*/
private $inProgress;
/**
* #var boolean
*
* #ORM\Column(name="accept_list", type="boolean")
*/
private $acceptList;
/**
* #var boolean
*
* #ORM\Column(name="visible_list", type="boolean")
*/
private $visibleList;
/**
* #var boolean
*
* #ORM\Column(name="many_loop", type="boolean")
*/
private $manyLoop;
/**
* #var string
*
* #ORM\Column(name="text_email", type="text", nullable=true)
*/
private $textEmail;
/**
* #var \DateTime
*
* #ORM\Column(name="created", type="datetime")
*/
private $created;
public function __construct()
{
$this->run = false;
$this->status = false;
$this->acceptList = true;
$this->visibleList = true;
$this->inProgress = false;
$this->manyLoop = true;
$this->created = new \Datetime('now');
$this->participants = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
*
* #return Project
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set content
*
* #param string $content
*
* #return Project
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* #return string
*/
public function getContent()
{
return $this->content;
}
/**
* Set place
*
* #param string $place
*
* #return Project
*/
public function setPlace($place)
{
$this->place = $place;
return $this;
}
/**
* Get place
*
* #return string
*/
public function getPlace()
{
return $this->place;
}
/**
* Set dateEvent
*
* #param \DateTime $dateEvent
*
* #return Project
*/
public function setDateEvent($dateEvent)
{
$this->dateEvent = $dateEvent;
return $this;
}
/**
* Get dateEvent
*
* #return \DateTime
*/
public function getDateEvent()
{
return $this->dateEvent;
}
/**
* Set status
*
* #param boolean $status
*
* #return Project
*/
public function setStatus($status)
{
$this->status = $status;
return $this;
}
/**
* Get status
*
* #return boolean
*/
public function getStatus()
{
return $this->status;
}
/**
* Set inProgress
*
* #param boolean $inProgress
*
* #return Project
*/
public function setInProgress($inProgress)
{
$this->inProgress = $inProgress;
return $this;
}
/**
* Get inProgress
*
* #return boolean
*/
public function getInProgress()
{
return $this->inProgress;
}
/**
* Set acceptList
*
* #param boolean $acceptList
*
* #return Project
*/
public function setAcceptList($acceptList)
{
$this->acceptList = $acceptList;
return $this;
}
/**
* Get acceptList
*
* #return boolean
*/
public function getAcceptList()
{
return $this->acceptList;
}
/**
* Set visibleList
*
* #param boolean $visibleList
*
* #return Project
*/
public function setVisibleList($visibleList)
{
$this->visibleList = $visibleList;
return $this;
}
/**
* Get visibleList
*
* #return boolean
*/
public function getVisibleList()
{
return $this->visibleList;
}
/**
* Set manyLoop
*
* #param boolean $manyLoop
*
* #return Project
*/
public function setManyLoop($manyLoop)
{
$this->manyLoop = $manyLoop;
return $this;
}
/**
* Get manyLoop
*
* #return boolean
*/
public function getManyLoop()
{
return $this->manyLoop;
}
/**
* Set textEmail
*
* #param string $textEmail
*
* #return Project
*/
public function setTextEmail($textEmail)
{
$this->textEmail = $textEmail;
return $this;
}
/**
* Get textEmail
*
* #return string
*/
public function getTextEmail()
{
return $this->textEmail;
}
/**
* Set created
*
* #param \DateTime $created
*
* #return Project
*/
public function setCreated($created)
{
$this->created = $created;
return $this;
}
/**
* Get created
*
* #return \DateTime
*/
public function getCreated()
{
return $this->created;
}
/**
* Set user
*
* #param \AppBundle\Entity\User $user
*
* #return Project
*/
public function setUser(\AppBundle\Entity\User $user)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return \AppBundle\Entity\User
*/
public function getUser()
{
return $this->user;
}
/**
* Add participant
*
* #param \AppBundle\Entity\Participant $participant
*
* #return Project
*/
public function addParticipant(\AppBundle\Entity\Participant $participant)
{
$this->participants[] = $participant;
// On lie l'annonce à la candidature
$participant->setProject($this);
return $this;
}
/**
* Remove participant
*
* #param \AppBundle\Entity\Participant $participant
*/
public function removeParticipant(\AppBundle\Entity\Participant $participant)
{
$this->participants->removeElement($participant);
}
/**
* Get participants
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getParticipants()
{
return $this->participants;
}}
My Form:
$builder ->add('participants', ParticipantType::class);
/**
* {#inheritdoc}
*/
public function configureOptions (OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Project',
'step' => 1,
));
}
Form imbricated :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('lastName', TextType::class, array(
'required' => true,
'label' => 'Nom'
))
->add('firstName', TextType::class, array(
'required' => true,
'label' => 'Prénom'
))
->add('email', EmailType::class, array(
'required' => true,
'label' => 'Adresse mail'
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => array('registration')
));
}
So here is to summarize my problem, I do not understand the error that Symfony returns, yet from the examples on the internet I do not see why there is a problem here ...
If someone to a solution would be top, because I blocked on this problem for a long time ...
Thank's !
Make sure that you don't forget to add : use Doctrine\Common\Collections\ArrayCollection or use $this->participants = new \Doctrine\Common\Collections\ArrayCollection();
In your FormType you should configure the participants field as a collection of Participant. It should be done using CollectionType like this:
$builder->add('participants', CollectionType::class, array(
'entry_type' => ParticipantType::class,
'allow_add' => true,
...
));
EDIT
According to your data model, the participants Field is a collection of Participant object. As you can see in the error message you presented, an array is expected. The CollectionType will always return an array of the target type.
That is why I suggest you to consider using a CollectionType in your form type. You can learn more about CollectionType here and about Symfony forms in general here

Symfony error when inserting new entity data

I am a beginner learning Symfony. I have an entity called sale for which I a form for inserting sales data.
The problem is each attempt to insert data results in the following error:
Neither the property "selectedstock" nor one of the methods
"addSelectedstock()"/"removeSelectedstock()", "setSelectedstock()",
"selectedstock()", "__set()" or "__call()" exist and have public
access in class "iCerge\Salesdeck\SalesBundle\Entity\Sale".
My Sale class is as follows:
<?php
namespace iCerge\Salesdeck\SalesBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Sales
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="iCerge\Salesdeck\SalesBundle\Entity\SalesRepository")
*/
class Sale
{
private $selectedstock;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var float
*
* #ORM\Column(name="cost", type="float")
*/
private $cost;
/**
* #var float
*
* #ORM\Column(name="profitloss", type="float")
*/
private $profitloss;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="datetime")
*/
private $date;
/**
* #ORM\ManyToOne(targetEntity="iCerge\Salesdeck\StockBundle\Entity\Stock", inversedBy="sales")
* #ORM\JoinColumn(name="sid", referencedColumnName="id")
*/
protected $stock;
/**
* #var integer
*
* #ORM\Column(name="number_of_purchases", type="integer")
*/
private $number_of_purchases;
/**
* #var float
*
* #ORM\Column(name="active_buy_price", type="float")
*/
private $active_buy_price;
/**
* #var integer
*
* #ORM\Column(name="sid", type="integer")
*/
private $sid;
/**
* Get sid
*
* #return integer
*/
public function getSid()
{
return $this->sid ? $this->sid : 0;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set cost
*
* #param float $cost
* #return Sales
*/
public function setCost($cost)
{
$this->cost = $cost;
return $this;
}
/**
* Get cost
*
* #return float
*/
public function getCost()
{
return $this->cost;
}
/**
* Set date
*
* #param \DateTime $date
* #return Sales
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
* Get date
*
* #return \DateTime
*/
public function getDate()
{
return $this->date;
}
/**
* Set profitloss
*
* #param float $profitloss
* #return Sales
*/
public function setProfitloss($profitloss)
{
$this->profitloss = $profitloss;
return $this;
}
/**
* Get profitloss
*
* #return float
*/
public function getProfitloss()
{
return $this->profitloss;
}
/**
* Set stock
*
* #param \iCerge\Salesdeck\StockBundle\Entity\Stock $stock
* #return Sale
*/
public function setStock(\iCerge\Salesdeck\StockBundle\Entity\Stock $stock = null)
{
$this->stock = $stock;
return $this;
}
/**
* Get stock
*
* #return \iCerge\Salesdeck\StockBundle\Entity\Stock
*/
public function getStock()
{
return $this->stock;
}
/********************/
/**
* Set number_of_purchases
*
* #return integer
*/
public function setNumberOfPurchases($purchases)
{
$this->number_of_purchases = $purchases;
return $this;
}
/**
* Get number_of_purchases
*
* #return integer
*/
public function getNumberOfPurchases()
{
return $this->number_of_purchases;
}
/**
* Set active_buy_price
*
* #return integer
*/
public function setActiveBuyPrice($price)
{
$this->active_buy_price = $price;
return $this;
}
/**
* Get active_buy_price
*
* #return integer
*/
public function getActiveBuyPrice()
{
return $this->active_buy_price;
}
public function getselectedstock()
{
return $this->selectedstock;
}
}
And the following is my Form class:
<?php
namespace iCerge\Salesdeck\SalesBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SalesType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('selectedstock', 'choice', array(
'choices' => $options['allow_extra_fields'],
'required' => true,
'empty_value' => 'Choose a Product',
'empty_data' => null,
'label' => 'Select Item',
// 'mapped'=>false
))
/*->add('test', 'choice', array(
'choices' => $options['allow_extra_fields'],
'required' => false,
'empty_value' => 'Choose your gender',
'empty_data' => null,
'mapped'=>false
))*/
->add('number_of_purchases', 'integer', array('label'=>'Number of purchases'))
->add('cost')
->add('profitloss', 'text', array('label'=>'Profit/Loss'))
->add('date', 'datetime', array('data'=>new \DateTime('now')))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
// 'data_class' => 'iCerge\Salesdeck\SalesBundle\Entity\Sales'
'data_class' => 'iCerge\Salesdeck\SalesBundle\Entity\Sale'
));
}
/**
* #return string
*/
public function getName()
{
return 'icerge_salesdeck_salesbundle_sales';
}
}
What am I doing wrong here?
Update
Screenshot shows dropdown list of available stock.
The form is the sales/add form which requires an item of available stock from the list to be selected.
You don't have a setter for selectedstock, and not related to the error you don't have annotation to mark selectedstock as an orm column, so it won't be persisted.

How to customise form rendering when based on entity field in symfony 2?

I have created a form based on an entity field. This form is composed of checkboxes that allow user to choose an entity (here 'car').
This works fine but I need to customize the rendering to get extra informations. Currently, the only information that is displayed is the 'id' attribute.
In my case, I would like to display extra entity informations in the form such as color, brand etc ...
Does anyone know how to proceed ?
The controller :
public function chooserAction() {
//symfony.com/doc/current/reference/forms/types/entity.html
$cars = $this->getDoctrine()
->getRepository('CarBundle:Car')
->find(1);
$formBuilder = $this->createFormBuilder();
/*
foreach ($cars as $car) {
$formBuilder->add($car->getId() ,'checkbox')->getForm();
}
*/
$formBuilder->add('cars', 'entity', array(
'class' => 'CarBundle:Car',
'property' => 'id',
'expanded' => 'true',
'multiple' => 'true',
));
$formBuilder->add('save', 'submit');
$form = $formBuilder->getForm();
$request = $this->get('request');
$form->handleRequest($request);
if ($form->isValid()) {
echo "ok";
// return $this->redirect($this->generateUrl('car_show', array('id' => $car->getId())));
}
return $this->render('CarBundle:Car:chooser.html.twig', array('form' => $form->createView()));
}
The entity :
<?php
namespace Foobar\CarBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Car
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Foobar\CarBundle\Entity\CarRepository")
*/
class Car
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="brand", type="string", length=255)
*/
private $brand;
/**
* #var string
*
* #ORM\Column(name="color", type="string", length=255)
*/
private $color;
/**
* #var integer
*
* #ORM\Column(name="power", type="integer")
*/
private $power;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Car
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set brand
*
* #param string $brand
* #return Car
*/
public function setBrand($brand)
{
$this->brand = $brand;
return $this;
}
/**
* Get brand
*
* #return string
*/
public function getBrand()
{
return $this->brand;
}
/**
* Set color
*
* #param string $color
* #return Car
*/
public function setColor($color)
{
$this->color = $color;
return $this;
}
/**
* Get color
*
* #return string
*/
public function getColor()
{
return $this->color;
}
/**
* Set power
*
* #param integer $power
* #return Car
*/
public function setPower($power)
{
$this->power = $power;
return $this;
}
/**
* Get power
*
* #return integer
*/
public function getPower()
{
return $this->power;
}
}
The view :
car chooser
{{ form(form) }}
You could create a toString() method in your Car entity.
public function __toString()
{
return '' . $this->getName();
}
If you want more thinks like pictures you could follow that http://symfony.com/doc/current/cookbook/form/form_customization.html

Symfony2/Doctrine2 OneToMany related entities do not have ID of the inverse side

I have 2 related entities, Invoice & InvoiceRow, and I have a form to let people create new invoices.
I also have some fixtures of test data that I load to seed the database with test data and no matter how I persist the entities to the database the invoice_id in InvoiceRow is always null
In the fixtures I've tried persisting the Invoice
InvoiceRow.php:
namespace Settleup\InvoiceBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
Class InvoiceRow {
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(type="string", length=100)
*/
protected $description;
/**
* The invoice row amount, stored in "bottle tops", ie £1.00 is 10000 bottle tops
*
* #ORM\Column(type="integer")
*/
protected $amount;
/**
* The invoice this row belongs to.
*
* #ORM\ManyToOne(targetEntity="Settleup\InvoiceBundle\Entity\Invoice", inversedBy="rows")
* #var integer
**/
protected $invoice;
public function setDescription($description) {
$this->description = $description;
}
public function getDescription() {
return $this->description;
}
public function setAmount($amount) {
$this->amount = $amount * 10000;
}
public function getAmount() {
return $this->amount / 10000;
}
/**
* Returns the invoice this row is attached to.
*
* #return Settleup\InvoiceBundle\Entity\Invoice The invoice.
*/
public function getInvoice() {
return $this->invoice;
}
/**
* Set the invoice this row belongs to.
*
* #param Settleup\InvoiceBundle\Entity\Invoice $newinvoice The invoice.
*/
public function setInvoice($invoice) {
$this->invoice = $invoice;
return $this;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Invoice.php
<?php
namespace Settleup\InvoiceBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Sylius\Bundle\AddressingBundle\Model\AddressInterface;
use Settleup\InvoiceBundle\Exception\MerchantUpdateException;
/**
* #ORM\Entity
*/
Class Invoice {
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(type="string", length=100)
*/
protected $reference;
/**
* #ORM\Column(type="datetime")
*/
protected $issueDate;
/**
* #ORM\OneToMany(targetEntity="Settleup\InvoiceBundle\Entity\InvoiceRow", mappedBy="invoice", cascade="persist")
*/
protected $rows;
/**
* #ORM\ManyToOne(targetEntity="Settleup\UserBundle\Entity\User")
*/
protected $merchant;
/**
* #ORM\ManyToOne(targetEntity="Settleup\InvoiceBundle\Entity\InvoiceAddress", cascade={"persist"})
*/
protected $invoiceAddress;
/**
* undocumented function
*
* #return void
* #author
**/
public function __construct()
{
$this->rows = new ArrayCollection();
$this->issueDate = new \DateTime();
}
/**
* undocumented function
*
* #return void
* #author
**/
public function getId()
{
return $this->id;
}
/**
* Returns all the rows on this invoice.
*
* #return ArrayCollection
* #author
**/
public function getRows()
{
return $this->rows;
}
/**
* Set the reference for this invoice.
*
* #return void
* #author
**/
public function setReference($reference)
{
$this->reference = $reference;
}
/**
* Returnt he invoice reference.
*
* #return string
* #author
**/
public function getReference()
{
return $this->reference;
}
/**
* Sets the date this invoice was issued.
*
* #param \DateTime $issueDate The date the invoice was issued.
* #return void
* #author
**/
public function setIssueDate(\DateTime $issueDate)
{
$this->issueDate = $issueDate;
}
/**
* Return the date the invoice was raised.
*
* #return \DateTime
* #author
**/
public function getIssueDate()
{
return $this->issueDate;
}
/**
* Sets the collections of rows that make up this invoice.
*
* #return void
* #author
**/
public function setRows(ArrayCollection $rows)
{
$this->rows = $rows;
}
/**
* Return the merchant for this invoice.
*
* #return Settleup\UserBundle\Entity\User
* #author
**/
public function getMerchant()
{
return $this->merchant;
}
/**
* Returns an instance of SyliusAddress, the address this invoice is for.
*
* #return Settleup\InvoiceBundle\Entity\InvoiceAddress
* #author
**/
public function getInvoiceAddress()
{
return $this->invoiceAddress;
}
/**
* Sets the address for this invoice
*
* #param Settleup\InvoiceBundle\Entity\InvoiceAddress invoiceAddress The address this invoice is for.
*
* #return void
* #author
**/
public function setInvoiceAddress(AddressInterface $invoiceAddress)
{
$this->invoiceAddress = $invoiceAddress;
}
/**
* Add rows
*
* #param \Settleup\InvoiceBundle\Entity\InvoiceRow $rows
* #return Invoice
*/
public function addRow(\Settleup\InvoiceBundle\Entity\InvoiceRow $rows)
{
$this->rows[] = $rows;
return $this;
}
/**
* Remove rows
*
* #param \Settleup\InvoiceBundle\Entity\InvoiceRow $rows
*/
public function removeRow(\Settleup\InvoiceBundle\Entity\InvoiceRow $rows)
{
$this->rows->removeElement($rows);
}
/**
* Set merchant
*
* #param \Settleup\UserBundle\Entity\User $merchant
* #return Invoice
*/
public function setMerchant(\Settleup\UserBundle\Entity\User $merchant = null)
{
if ($this->id == null) {
$this->merchant = $merchant;
} else {
throw new MerchantUpdateException("Unable to update the merchant for this invoice, you can only assign a merchant when creating the invoice.");
}
return $this;
}
/**
* #TODO Find out why this method is needed.
*
* #return void
* #author
**/
public function getAddresses()
{
}
public function setAddresses() {}
}
I've got a form that I use to populate rows in both tables:
class InvoiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('reference')
->add('rows', 'collection', array(
'type' => 'invoice_row',
'allow_add' => true,
'by_reference' => false
))
->add('invoiceAddress', 'sylius_address');
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) {
$form = $event->getForm();
$form->add('merchant', 'hidden');
}
);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Settleup\InvoiceBundle\Entity\Invoice',
'cascade_validation' => true
));
}
public function getName()
{
return 'settleup_invoicebundle_invoicetype';
}
}
When that form is saved using an action on the controller:
public function createAction(Request $request)
{
$entity = new Invoice();
$form = $this->createForm(new InvoiceType(), $entity);
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$entity->setMerchant($this->getUser());
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('invoice_show', array('id' => $entity->getId())));
} else {
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
both rows have all the correct data except InvoiceRow is missing the invoice_id.
It's a bit late but may be useful for others
1) Try persisting the owning side (the one with FK) i.e. invoiceRow
2) I had issues persisting a related entity because the id field didn't follow the defaults. Adding this made it work:
* #ORM\ManyToOne(targetEntity="Personas", fetch="EAGER", inversedBy="cuentasCorriente",
* cascade={"persist", "refresh"})
* #ORM\JoinColumn(name="persona_id", referencedColumnName="ID", nullable=false)
referencedColumnName="ID" see "ID" != "id"
as I added Doctrine to a legacy system, had no choice.

Odd many-to-many form rendering with symfony and doctrine

I'm hoping this is possible using some built-in libraries such as the form builder. I have the following three entities. The one in the middle is almost just a regular join table but it has an extra column with an extra piece of data.
Formula --< FormulaColor >-- Color
FormulaColor has the fields: formula, color, and percentage.
The percentage field is to say what percentage a color makes up of a given formula. A very simple example is that a Formula may be 77% red and 33% blue. My problem is that I want to choose the colors for a Formula, and give them a percentage manually using forms. So I would add (or edit) a certain formula and give it say the color violet (20%) green (45%) and yellow (35%). I do not care about being able to add new colors in the formula add/edit view. I just want to be able to select existing colors. I've been playing around with it for hours with collection and entity types, but no luck.
Got any pointers or tips for me? Will I have to do it manually without the form component etc?
Thanks.
formula form type
class FormulaAddEditType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('code', null, array(
'label' => 'Code'
))
->add('name', null, array(
'label' => 'Name'
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Prism\Portal\CommonBundle\Entity\Formula'
));
}
public function getName()
{
return 'prism_portal_adminbundle_formulaaddedittype';
}
}
Formula Entity
class Formula
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*
* #Serializer\Expose
*/
private $id;
/**
* #var string $code
*
* #ORM\Column(name="code", type="string", length=50, nullable=true)
*
* #Serializer\Expose
*/
private $code;
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=50, nullable=true)
*/
private $name;
/**
* #var datetime $createdOn
*
* #Gedmo\Timestampable(on="create")
* #ORM\Column(name="createdOn", type="datetime", nullable=true)
*/
private $createdOn;
/**
* #var datetime $updatedOn
*
* #Gedmo\Timestampable(on="update")
* #ORM\Column(name="updatedOn", type="datetime", nullable=true)
*/
private $updatedOn;
/**
* #var formulaColors
*
* #ORM\OneToMany(targetEntity="FormulaColor", mappedBy="formula")
*/
private $formulaColors;
public function __construct()
{
$this->formulaColors = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set code
*
* #param string $code
*/
public function setCode($code)
{
$this->code = $code;
}
/**
* Get code
*
* #return string
*/
public function getCode()
{
return $this->code;
}
/**
* Set name
*
* #param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set createdOn
*
* #param datetime $createdOn
*/
public function setCreatedOn($createdOn)
{
$this->createdOn = $createdOn;
}
/**
* Get createdOn
*
* #return datetime
*/
public function getCreatedOn()
{
return $this->createdOn;
}
/**
* Set updatedOn
*
* #param datetime $updatedOn
*/
public function setUpdatedOn($updatedOn)
{
$this->updatedOn = $updatedOn;
}
/**
* Get updatedOn
*
* #return datetime
*/
public function getUpdatedOn()
{
return $this->updatedOn;
}
/**
* Add formulaColor
*
* #param FormulaColor $formulaColor
*/
public function addFormulaColor(FormulaColor $formulaColor)
{
$this->formulaColors[] = $formulaColor;
}
/**
* Get formulaColors
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFormulaColors()
{
return $this->formulaColors;
}
}
FormulaColor Entity
/**
* Prism\Portal\CommonBundle\Entity\FormulaColor
*
* #ORM\Table(name="FormulaColor")
* #ORM\Entity
*
* #Serializer\ExclusionPolicy("all")
*/
class FormulaColor
{
/**
* #var integer $formula
*
* #ORM\ManyToOne(targetEntity="Formula", inversedBy="formulaColors")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="formulaId", referencedColumnName="id")
* })
* #ORM\Id
*/
private $formula;
/**
* #var integer color
*
* #ORM\ManyToOne(targetEntity="Color", inversedBy="formulaColors")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="colorId", referencedColumnName="id")
* })
* #ORM\Id
*/
private $color;
/**
* #var decimal percentage
*
* #ORM\Column(type="decimal", precision=5, scale=2, nullable=false)
*/
private $percentage;
/**
* Set percentage
*
* #param decimal $percentage
*/
public function setPercentage($percentage)
{
$this->percentage = $percentage;
}
/**
* Get percentage
*
* #return decimal
*/
public function getPercentage()
{
return $this->percentage;
}
/**
* Set formula
*
* #param Prism\Portal\CommonBundle\Entity\Formula $formula
*/
public function setFormula(\Prism\Portal\CommonBundle\Entity\Formula $formula)
{
$this->formula = $formula;
}
/**
* Get formula
*
* #return Prism\Portal\CommonBundle\Entity\Formula
*/
public function getFormula()
{
return $this->formula;
}
/**
* Set color
*
* #param Prism\Portal\CommonBundle\Entity\Color $color
*/
public function setColor(\Prism\Portal\CommonBundle\Entity\Color $color)
{
$this->color = $color;
}
/**
* Get color
*
* #return Prism\Portal\CommonBundle\Entity\Color
*/
public function getColor()
{
return $this->color;
}
}
Color Entity
/**
* Prism\Portal\CommonBundle\Entity\Color
*
* #ORM\Table(name="Color")
* #ORM\Entity
*
* #Serializer\ExclusionPolicy("all")
*/
class Color
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string $code
*
* #ORM\Column(name="code", type="string", length=50, nullable=true)
*/
private $code;
/**
* #var string $hexColor
*
* #ORM\Column(name="hex_color", type="string", length=50, nullable=true)
*/
private $hexColor;
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=50, nullable=true)
*/
private $name;
/**
* #var integer $sortOrder
*
* #ORM\Column(name="sortOrder", type="integer", nullable=true)
*/
private $sortOrder;
/**
* #var datetime $createdOn
*
* #ORM\Column(name="createdOn", type="datetime", nullable=true)
*/
private $createdOn;
/**
* #var datetime $updatedOn
*
* #ORM\Column(name="updatedOn", type="datetime", nullable=true)
*/
private $updatedOn;
/**
* #var $formulaColors
*
* #ORM\OneToMany(targetEntity="FormulaColor", mappedBy="color")
*/
private $formulaColors;
public function __construct()
{
$this->formulaColors = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set code
*
* #param string $code
*/
public function setCode($code)
{
$this->code = $code;
}
/**
* Get code
*
* #return string
*/
public function getCode()
{
return $this->code;
}
/**
* Set name
*
* #param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set sortOrder
*
* #param integer $sortOrder
*/
public function setSortOrder($sortOrder)
{
$this->sortOrder = $sortOrder;
}
/**
* Get sortOrder
*
* #return integer
*/
public function getSortOrder()
{
return $this->sortOrder;
}
/**
* Set createdOn
*
* #param datetime $createdOn
*/
public function setCreatedOn($createdOn)
{
$this->createdOn = $createdOn;
}
/**
* Get createdOn
*
* #return datetime
*/
public function getCreatedOn()
{
return $this->createdOn;
}
/**
* Set updatedOn
*
* #param datetime $updatedOn
*/
public function setUpdatedOn($updatedOn)
{
$this->updatedOn = $updatedOn;
}
/**
* Get updatedOn
*
* #return datetime
*/
public function getUpdatedOn()
{
return $this->updatedOn;
}
/**
* Get formulaColors
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFormulaColors()
{
return $this->formulaColors;
}
/**
* addFormulaColors
*
* #param \Prism\Portal\CommonBundle\Entity\FormulaColor $formulaColor
*/
public function addFormulaColor(\Prism\Portal\CommonBundle\Entity\FormulaColor $formulaColor)
{
$this->formulaColors[] = $formulaColor;
}
/**
* Remove formulaColors
*
* #param \Prism\Portal\CommonBundle\Entity\FormulaColor $formulaColors
*/
public function removeFormulaColor(\Prism\Portal\CommonBundle\Entity\FormulaColor $formulaColors)
{
$this->formulaColors->removeElement($formulaColors);
}
/**
* Set hexColor
*
* #param string $hexColor
* #return Color
*/
public function setHexColor($hexColor)
{
$this->hexColor = $hexColor;
return $this;
}
/**
* Get hexColor
*
* #return string
*/
public function getHexColor()
{
return $this->hexColor;
}
}
I also have a ColorAddEditType form
class ColorAddEditType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('code', null, array(
'label' => 'Code'
))
->add('name', null, array(
'label' => 'Name'
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Prism\Portal\CommonBundle\Entity\Color'
));
}
public function getName()
{
return 'prism_portal_adminbundle_coloraddedittype';
}
}
I've also updated my code according to Ryan's response.
FormulaColorType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('color', new ColorAddEditType());
$builder->add('percent', 'number');
}
FormulaAddEditType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('code', null, array(
'label' => 'Code'
))
->add('name', null, array(
'label' => 'Name'
));
$builder->add('formulaColors', 'collection', array(
'type' => new FormulaColorType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
));
}
and now I'm getting use a transformer / set data_class to null exceptions. When I change the one to null, then it says I can change the other to null. When I do that it basically says to change it back. Is it normal that I should have to set up a data transformer for something like this?
Here is the current error I'm getting:
The form's view data is expected to be an instance of class
Prism\Portal\CommonBundle\Entity\Color, but is an instance of class
Prism\Portal\CommonBundle\Entity\FormulaColor. You can avoid this
error by setting the "data_class" option to null or by adding a view
transformer that transforms an instance of class
Prism\Portal\CommonBundle\Entity\FormulaColor to an instance of
Prism\Portal\CommonBundle\Entity\Color.
You should be able to do something like this. It's a fairly common pattern. The tricky bit will be the Javascript, but it won't be too bad. You might need to make your numbers add to 100 using Javascript as well.
FormulaType
public function buildForm(FormBuilder $builder, array $options) {
$builder->add('formulaColors', 'collection', array(
'type' => new FormularColorType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
));
}
FormulaColorType
public function buildForm(FormBuilder $builder, array $options) {
$builder->add('color', new ColorType()); // Or similar
$builder->add('percent', 'number');
}

Resources