symfony2 embedded form collection and entity mapping - php

I was following the symfony2 tutorial on how to create an embedded form collection but wasn't able to implement it since it only creates a junction table.
According to doctrine2 documentation:
"Why are many-to-many associations less common? Because frequently you want to associate additional attributes with an association, in which case you introduce an association class. Consequently, the direct many-to-many association disappears and is replaced by one-to-many/many-to-one associations between the 3 participating classes."
Here are some snippets of my code:
src/AppBundle/Entity/Ingredient.php
/**
* Defines the properties of the Ingredient entity to represent the portal ingredients.
*
* #author furious_snail
*
* #ORM\Entity()
* #ORM\Table(name="ingredients")
* #UniqueEntity("name")
*/
class Ingredient
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="IngredientCategory")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=true)
*/
private $category;
/**
* #ORM\Column(type="string", unique=true)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="IngredientNutrient", mappedBy="ingredient", cascade={"persist", "remove"})
*/
private $nutrientsPer100G;
public function __construct()
{
$this->substitute = new ArrayCollection();
$this->nutrientsPer100G = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
/**
* #param mixed $nutrientsPer100G
*/
public function setNutrientsPer100G($nutrientsPer100G)
{
$this->nutrientsPer100G = $nutrientsPer100G;
}
/**
* #return array
*/
public function getNutrientsPer100G()
{
return $this->nutrientsPer100G;
}
}
src/AppBundle/Entity/IngredientNutrient.php
/**
* #ORM\Entity()
* #ORM\Table(name="ingredient_nutrient")
*/
class IngredientNutrient
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="Ingredient", inversedBy="nutrientsPer100G")
* #ORM\JoinColumn(name="ingredient_id", referencedColumnName="id", nullable=true)
*/
protected $ingredient;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="Nutrient")
* #ORM\JoinColumn(name="nutrient_id", referencedColumnName="id", nullable=true)
*/
protected $nutrient;
/**
* #ORM\Column(type="float")
*/
private $quantity;
/**
* #ORM\ManyToOne(targetEntity="Unit")
* #ORM\JoinColumn(name="unit_id", referencedColumnName="id", nullable=true)
*/
private $unit;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #return mixed
*/
public function getIngredient()
{
return $this->ingredient;
}
/**
* #param mixed $ingredient
*/
public function setIngredient($ingredient)
{
$this->ingredient = $ingredient;
}
/**
* #return mixed
*/
public function getNutrient()
{
return $this->nutrient;
}
/**
* #param mixed $nutrient
*/
public function setNutrient($nutrient)
{
$this->nutrient = $nutrient;
}
/**
* #return mixed
*/
public function getQuantity()
{
return $this->quantity;
}
/**
* #param mixed $quantity
*/
public function setQuantity($quantity)
{
$this->quantity = $quantity;
}
/**
* #return mixed
*/
public function getUnit()
{
return $this->unit;
}
/**
* #param mixed $unit
*/
public function setUnit($unit)
{
$this->unit = $unit;
}
}
src/AppBundle/Form/Type/IngredientNutrientType.php
class IngredientNutrientType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nutrient', 'entity', array(
'class' => 'AppBundle\Entity\Nutrient',
'choice_label' => 'name',
'label' => 'Nutrient',
))
->add('quantity', null, array('label' => 'Cantitate'))
->add('unit', 'entity', array(
'class' => 'AppBundle\Entity\Unit',
'choice_label' => 'unit',
'label' => 'Unitate de masura'
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\IngredientNutrient',
));
}
public function getName()
{
return 'app_ingredient_nutrient';
}
}
src/AppBundle/Form/Type/IngredientType.php
class IngredientType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', null, array('label' => 'Name'))
->add('nutrients_per_100_g', 'collection', array(
'type' => new IngredientNutrientType(),
'allow_add' => true,
'label' => 'Nutrient quantity per 100g',
'options' => array('label' => false),
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Ingredient',
));
}
public function getName()
{
return 'app_ingredient';
}
}
This works, I do get an embedded form collection but the issue is that the ingredient_id in the ingredient_nutrient table is null. How do I make it to fill the table with the right ID?
These are the fields I get on the page:
Name:
Nutrient:
Quantity:
Unit:
The idea is that if I have IngredientNutrient form tied with Ingredient form the user shouldn't have to specify ingredient name twice.
Thank you.

To start with, you need to "cross reference" your entities:
public function setNutrientsPer100G($nutrientsPer100G)
{
$this->nutrientsPer100G = $nutrientsPer100G;
$nutrientsPer100G->setIngrediant($this);
}
Make sure both sides of the relation are being set. That will take care of the null id issues.
The other problem is that you are using collections in your Ingrediant/Nutrient entities but your set methods are not using the array operators.

Related

Symfony Data Transform and Update Form With The Data

Between 2 entities exists OneToMany relationship. First is User Entity second is Domains entities. One user can have multiple domains.
This is Users Entity (removed other fields because unrelated with subject):
class Users extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="\UsersBundle\Entity\UserDomains" , mappedBy="user" , cascade={"all"})
*/
protected $domains;
/**
* Add domain
*
* #param \UsersBundle\Entity\UserDomains $domain
*
* #return Users
*/
public function addDomain(\UsersBundle\Entity\UserDomains $domain)
{
$this->domains[] = $domain;
return $this;
}
/**
* Remove domain
*
* #param \UsersBundle\Entity\UserDomains $domain
*/
public function removeDomain(\UsersBundle\Entity\UserDomains $domain)
{
$this->domains->removeElement($domain);
}
}
This is UserDomains Entity (some fields has been removed):
class UserDomains
{
public function __construct() {
$this->setCreatedAt(new \DateTime());
}
public function __toString()
{
return $this->name;
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="AffiliateBundle\Entity\Users", inversedBy="domains")
* #ORM\JoinColumn(name="user", referencedColumnName="id", onDelete="CASCADE")
*/
private $user;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set user
*
* #param integer $user
*
* #return UserDomains
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return integer
*/
public function getUser()
{
return $this->user;
}
/**
* Set name
*
* #param string $name
*
* #return UserDomains
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
}
Must be add a domain when register user to system. I have got RegisterType for registration form. This type form class has got DataTransformer for adding domain which register user.
RegisterType class is here:
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fullName', 'text', array('attr' => array('class' => 'form-control'), 'label' => 'user_register.fullname', 'translation_domain' => 'UsersBundle'))
->add('domains', 'text', array('attr' => array('class' => 'form-control select2', 'id' => 'select2_sample1')))
;
$builder->get('domains')->addModelTransformer(new CallbackTransformer(
function(){
return '';
},
function($data){
$arrCollection = new ArrayCollection();
if (strpos($data, ",") !== false) {
$expData = explode(',', $data);
foreach ($expData as $domain) {
$domainObj = new UserDomains();
$domainObj->setName($domain);
$domainObj->setEnabled(true);
$arrCollection->add($domainObj);
}
} else if (!empty($data)) {
$domain = new UserDomains();
$domain->setName($data);
$domain->setEnabled(true);
$arrCollection->add($domain);
}
return $arrCollection;
}
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\Users',
'intention' => 'registration',
));
}
public function getParent()
{
return 'fos_user_registration';
// Or for Symfony < 2.8
// return 'fos_user_registration';
}
public function getName()
{
return 'user_registration';
}
}
So anyone fill the register form and submit. User created and adding the domains to user_domains table but doesn't update the user field which must be new user's id. How to do this with automatically? Or have you got any idea to update this user column of user_domains table with the new user's id?
Thanks for helps to all StackoverFlow :)
Add __construct like this to RegistrationType:
class RegistrationType extends AbstractType
{
private $user;
public function __construct(Users $user) {
$this->user = $user;
}
add setUser function to your DataTransform like this:
$domainObj->setUser($this->user);
//and
$domain->setUser($this->user);
finally update your controller like this:
$form = $this->createForm(new RegistrationType($userEntity), $userEntity);

CollectionType entities issue

Well I'm trying to create a form which can handle multiple file uploads. So far so good. I have two tables image and product which has ManyToOne relationship. I'm using CollecitonType field for the images so I can dynamically add/remove fields on the fly. The problem is that I get this exception on form rendering
Neither the property "imageFile" nor one of the methods "getImageFile()", "imageFile()", "isImageFile()", "hasImageFile()", "__get()" exist and have public access in class "AppBundle\Entity\Product".
What I'm doing wrong?
AppBundle\Entity\Image
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Product
*
* #ORM\Table()
* #ORM\Entity
*/
class Image
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Product")
*/
protected $product;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="product_image", fileNameProperty="imageName")
*
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
private $imageName;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
private $updatedAt;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*/
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTime('now');
}
}
/**
* #return File
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param string $imageName
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
}
/**
* #return string
*/
public function getImageName()
{
return $this->imageName;
}
/**
* Set updatedAt
*
* #param \DateTime $updatedAt
*
* #return Image
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Get updatedAt
*
* #return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Set product
*
* #param \AppBundle\Entity\Product $product
*
* #return Image
*/
public function setProduct(\AppBundle\Entity\Product $product = null)
{
$this->product = $product;
return $this;
}
/**
* Get product
*
* #return \AppBundle\Entity\Product
*/
public function getProduct()
{
return $this->product;
}
}
AppBundle\Entity\Product
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection ;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table()
* #ORM\Entity
*/
class Product
{
/**
* #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;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Product
*/
private $image;
/**
* Constructor
*/
public function __construct()
{
$this->image = new ArrayCollection;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
public function getImages()
{
return $this->images;
}
public function addImage(ImageInterface $image)
{
if (!$this->images->contains($image)) {
$this->images->add($image);
}
return $this;
}
public function removeImage(ImageInterface $image)
{
$this->images->remove($image);
return $this;
}
public function setImages(Collection $images)
{
$this->images = $images;
}
}
AppBundle\Form\ImageType
..............
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('imageFile', 'file')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Image',
'attr'=> array('novalidate'=>'novalidate')
));
}
.............
AppBundle\Form\ProductType
...........
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text')
->add('imageFile', 'collection', array(
'allow_add' => true,
'allow_delete' => true,
'required' => false,
'type' => new ImageType(),
'prototype' => true,
'attr' => array(
'class' => 'selection',
),
))
->add('upload', 'submit')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Product',
'attr'=> array('novalidate'=>'novalidate')
));
}
......
First :
In your Product class:
Your variable is named private $image, it should be private $images; with the 'S' for plurial.
Second :
In your product form builder, you ask for a field named ImageFile, but it doesn't exist in Product class (but exists on Image class), it should be images.

Symfony2 error on submitting form with a collection of forms - "Warning: spl_object_hash() expects parameter 1 to be object, array given"

I have an Employee class which has a OneToMany relation to the PhoneNumber class, and I'm using form builders, and using prototypes to embed multiple phone numbers into the New Employee form, with javascript.
From dumping my employee variable, I see that each submitted PhoneNumber is represented as an array, when I suppose it should be converted to a PhoneNumber object when the submitted data is processed.
My entitites are:
/**
* Employee
*
* #ORM\Table(uniqueConstraints={#UniqueConstraint(name="employee_username_idx", columns={"username"})})
* #ORM\Entity(repositoryClass="Acme\BambiBundle\Entity\EmployeeRepository")
*/
class Employee
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var array
*
* #OneToMany(targetEntity="PhoneNumber", mappedBy="employee", cascade={"persist","remove"}, fetch="EAGER")
**/
private $phoneNumbers;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Constructor
*/
public function __construct()
{
$this->phoneNumbers = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add phoneNumbers
*
* #param \Acme\BambiBundle\Entity\PhoneNumber $phoneNumbers
* #return Employee
*/
public function addPhoneNumber(\Acme\BambiBundle\Entity\PhoneNumber $phoneNumbers)
{
$phoneNumbers->setEmployee($this);
$this->phoneNumbers[] = $phoneNumbers;
return $this;
}
/**
* Remove phoneNumbers
*
* #param \Acme\BambiBundle\Entity\PhoneNumber $phoneNumbers
*/
public function removePhoneNumber(\Acme\BambiBundle\Entity\PhoneNumber $phoneNumbers)
{
$this->phoneNumbers->removeElement($phoneNumbers);
}
/**
* Get phoneNumbers
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPhoneNumbers()
{
return $this->phoneNumbers;
}
// ...
}
and
/**
* PhoneNumber
*
* #ORM\Table()
* #ORM\Entity
*/
class PhoneNumber
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="type", type="text", nullable=true)
*/
private $type;
/**
* #var string
*
* #ORM\Column(name="number", type="text")
*/
private $number;
/**
* #var Employee
*
* #ManyToOne(targetEntity="Employee", inversedBy="phoneNumbers")
* #JoinColumn(name="employee_id", referencedColumnName="id", onDelete="CASCADE")
**/
private $employee;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set number
*
* #param string $number
* #return PhoneNumber
*/
public function setNumber($number)
{
$this->number = $number;
return $this;
}
/**
* Get number
*
* #return string
*/
public function getNumber()
{
return $this->number;
}
/**
* Set employee
*
* #param \Acme\BambiBundle\Entity\Employee $employee
* #return PhoneNumber
*/
public function setEmployee(\Acme\BambiBundle\Entity\Employee $employee = null)
{
$this->employee = $employee;
return $this;
}
/**
* Get employee
*
* #return \Acme\BambiBundle\Entity\Employee
*/
public function getEmployee()
{
return $this->employee;
}
/**
* Set type
*
* #param string $type
* #return PhoneNumber
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* Get type
*
* #return string
*/
public function getType()
{
return $this->type;
}
}
My form types are:
class EmployeeType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('phoneNumbers', 'collection', array(
'type' => new PhoneNumberType(),
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'prototype_name' => 'phoneNumberPrototype',
'by_reference' => false,
))
// ...
->add('save', 'submit', array('attr' => array('class' => 'btn btnBlue')));
}
public function getName() {
return 'employee';
}
}
and
class PhoneNumberType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('type', 'text', array('required' => false, 'label' => 'Type (optional)'))
->add('number', 'text');
}
public function getName() {
return 'phoneNumber';
}
}
Are there any things I could try to solve the problem? Is there anything that I am obviously doing wrong?
The solution was in a detail that I didn't know about - setting a data_class in the PhoneNumberType class:
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Acme\BambiBundle\Entity\PhoneNumber',
));
}
Without this, the code that processes the data from the submitted form does not know that each phone number should be converted to a PhoneNumber class, and thus they stay as arrays.
Please bear in mind that I am using Symfony 2.6. In Symfony 2.7 the above function is called configureOptions and has a slightly different definition:
public function configureOptions(OptionsResolver $resolver) {

Doctrine: referenced object always null OneToMany / ManyToOne cascade

Please tell me why the value of field 'pool_id' is always NULL in 'Question' Table?
(To add fiels 'Question' in the form used JS)
Pool.php
use Acme\ExamBundle\Entity\Question;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Pool
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $name;
/**
* #ORM\OneToMany(targetEntity="Question", mappedBy="pools",cascade={"persist"})
*/
protected $questions;
public function __construct()
{
$this->questions = new ArrayCollection();
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getQuestions()
{
return $this->questions;
}
/**
* Add question
*
* #param \Acme\ExamBundle\Entity\Question $question
* #return Pool
*/
public function addQuestion(Question $question)
{
/*$question->addPool($this);
$this->questions->add($question);*/
if (!$this->questions->contains($question)) {
$this->questions->add($question);
}
}
/**
* Remove question
*
* #param \Acme\ExamBundle\Entity\Question $question
*/
public function removeQuestion(Question $question)
{
$this->questions->removeElement($question);
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set questions
*
* #param \Acme\ExamBundle\Entity\Question $questions
* #return Pool
*/
public function setQuestions(Question $questions = null)
{
$this->questions = $questions;
return $this;
}
}
Question.php
/**
* #ORM\Entity
*/
class Question
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="text")
*/
protected $description;
/**
* #ORM\Column(type="string")
*/
protected $variants;
/**
* #ORM\Column(type="string")
*/
protected $ansver;
/**
* #ORM\ManyToOne(targetEntity="Pool", inversedBy="questions")
* #ORM\JoinColumn(name="pool_Id", referencedColumnName="id")
*/
protected $pools;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set description
*
* #param string $description
* #return Question
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set variants
*
* #param string $variants
* #return Question
*/
public function setVariants($variants='ok')
{
$this->variants = $variants;
return $this;
}
/**
* Get variants
*
* #return string
*/
public function getVariants()
{
return $this->variants;
}
/**
* Set ansver
*
* #param string $ansver
* #return Question
*/
public function setAnsver($ansver)
{
$this->ansver = $ansver;
return $this;
}
/**
* Get ansver
*
* #return string
*/
public function getAnsver()
{
return $this->ansver;
}
/**
* Set pools
*
* #param string $pools
* #return Question
*/
public function setPools($pools)
{
$this->pools = $pools;
return $this;
}
/**
* Get pools
*
* #return string
*/
public function getPools()
{
return $this->pools;
}
}
QuestionType
class QuestionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description');
$builder->add('variants');
$builder->add('ansver');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\ExamBundle\Entity\Question',
));
}
public function getName()
{
return 'question';
}
}
PoolType
class PoolType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
$builder->add('questions', 'collection', array(
'type' => new QuestionType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\ExamBundle\Entity\Pool',
));
}
public function getName()
{
return 'pool';
}
}
Thanks for your thoughts. Hope you can help me.
class Pool
/**
* #ORM\OneToMany(targetEntity="Question", mappedBy="pools",cascade={"persist"})
*/
protected $questions;
class Question
/**
* #ORM\ManyToOne(targetEntity="Pool", inversedBy="questions")
* #ORM\JoinColumn(name="pool_Id", referencedColumnName="id")
*/
protected $pools;
Using $pools to represent one pool is a bit confusing. You do understand that a OneToMany relation implies that a question can belong to at most one pool?
The reason that Question::pool_id is null is that you are not calling Question::setPools. Fix this by tweaking Pool:addQuestion
class Pool
public function addQuestion(Question $question)
{
/*$question->addPool($this);
$this->questions->add($question);*/
if (!$this->questions->contains($question)) {
$this->questions->add($question);
$question->setPools($this); // ADD THIS LINE
}
}
Looks like you almost had it in there. Maybe you changed the relation from a ManyToMany?
Also, change the column name from pool_Id to pool_id. SQL is not always consistent when dealing with mixed case names.
Finally, might want to change Ansver to Answer

Symfony2 form collection field not showing up

I'll explain what I'm trying to do.
I have the following entities, Dealers Brands and Types.
1 Dealer can have many brands associated and each relation will have one type associated as well, that's why is a manyToMany relationship with an intermediate table.
I want to create a Form to Add dealers to our system and when doing so they can choose which brands are associated (there are few brands so we want to display them as checkboxes) and of what type, but trying to show this form with the brands is been tricky until now.
Brand Entity
/**
* Brand
*
* #ORM\Table()
* #ORM\Entity
*/
class Brand
{
/**
* #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;
/**
* #ORM\ManyToMany(targetEntity="TypeBrand")
* #ORM\JoinTable(name="brand_type",
* joinColumns={#ORM\JoinColumn(name="brand_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="type_id", referencedColumnName="id")}
* )
*/
private $type;
/**
* #ORM\OneToMany(targetEntity="DealerBrand", mappedBy="brand")
*/
private $dealerBrand;
public function __construct()
{
$this->type = new ArrayCollection();
$this->dealerBrand = new ArrayCollection();
}
public function __toString()
{
return $this->getName();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Brand
*/
public function setName($name)
{
$this->name = $name;
$this->setSlug($name);
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
public function addType(TypeBrand $type)
{
$this->type[] = $type;
}
public function getType()
{
return $this->type;
}
public function addDealerBrand($dealerBrand)
{
$this->dealerBrand[] = $dealerBrand;
}
public function getDealerBrand()
{
return $this->dealerBrand;
}
}
Dealer Entity
/**
* Dealer
*
* #ORM\Table("dealer")
* #ORM\Entity(repositoryClass="Project\DealersBundle\Entity\Repository\DealerRepository")
* #Gedmo\Loggable
*/
class Dealer
{
/**
* #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, unique=true)
* #Assert\NotBlank()
* #Assert\Length(min = "10")
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="DealerBrand", mappedBy="dealer")
*
*/
private $dealerBrand;
public function __construct()
{
$this->dealerBrand = new ArrayCollection();
}
public function __toString()
{
return $this->getName();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Dealer
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
public function addDealerBrand($dealerBrand)
{
$this->dealerBrand[] = $dealerBrand;
}
public function getDealerBrand()
{
return $this->dealerBrand;
}
}
DealerBrand Entity
/**
* DealerBrand
*
* #ORM\Table("dealer_brand")
* #ORM\Entity
*/
class DealerBrand
{
/**
* #var integer
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Dealer", inversedBy="dealerBrand")
*/
private $dealer;
/**
* #var integer
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Brand", inversedBy="dealerBrand")
*/
private $brand;
/**
* Set dealer
*
* #param integer $dealer
* #return DealerBrand
*/
public function setDealer($dealer)
{
$this->dealer = $dealer;
return $this;
}
/**
* Get dealer
*
* #return integer
*/
public function getDealer()
{
return $this->dealer;
}
/**
* Set brand
*
* #param integer $brand
* #return DealerBrand
*/
public function setBrand($brand)
{
$this->brand = $brand;
return $this;
}
/**
* Get brand
*
* #return integer
*/
public function getBrand()
{
return $this->brand;
}
}
Now this are my formtypes
DealerBrandType
class DealerBrandType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('brand', 'entity', array(
'class' => 'Project\DealersBundle\Entity\Brand',
'property' => 'name',
'multiple' => true,
'expanded' => true,
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Project\DealersBundle\Entity\DealerBrand'
)
);
}
public function getName()
{
return 'DealerBrand';
}
}
DealerType
class DealerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('dealerBrand', 'collection', array(
'type' => new DealerBrandType(),
'allow_add' => true,
'allow_delete' => true,
))
->add('Save', 'submit');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Project\DealersBundle\Entity\Dealer'
)
);
}
public function getName()
{
return 'dealerType';
}
}
And this is my controller
public function addAction()
{
$dealer = new Dealer();
$form = $this->createForm(new DealerType(), $dealer);
$form->handleRequest($this->getRequest());
if ($form->isValid()) {
$this->getDoctrine()->getManager()->persist($dealer);
$this->getDoctrine()->getManager()->flush();
return $this->redirect($this->generateUrl('dealers_list'));
}
return $this->render(
'ProjectDealersBundle:Dealers:AddDealer.html.twig',
array(
'form' => $form->createView()
)
);
}
If this is not the correct approach please tell me, tell me also if you see bad code, that way I can improve
* EDIT *
This is the result i need
http://tinypic.com/r/24pkzdc/8
You can see there the brands and so .. so the idea is that when saving the Dealer you also save the association with the brands
* END EDIT *
Thank you!
I'm not sure this is the best answer ever but you can achieve it by making a custom query in your form :
->add('contact', 'entity', array(
'class' => 'AcmeBundle:Entity'
'query_builder' => function ( \acme\bundle\Entity\entityRepository $c){
$qb = $c->createQueryBuilder('a');
return $qb->orderBy('a.nom', 'ASC')
->join('a.categories', 'c')
->where( $qb->expr()->in ( 'c.id', ':listCategories') )
->setParameter( 'listCategories', array (
7,
));
},
'attr' => array( 'class' => 'other')
))
;
}
by using an 'entity' field type, i can inject custom query with the option 'query_builder' ( for more information : http://symfony.com/doc/current/reference/forms/types/entity.html )
then, inside i declare an anonymous function ( http://php.net/manual/en/functions.anonymous.php ) to create my custom query with the createQueryBuilder.
With the createQueryBuilder , you create your custom query ( here i had almost like you a many to many relationship and i wanted to get only some of them with a filtering array ( the set parameter ) .
here is the doc for custom query : http://symfony.com/doc/current/book/doctrine.html
the results of the query, if not null , will be displayed in your form
/** ALTERNATIVE ANSWER **/
if you want to display your dealers AND brands with a tree in your select then your have to :
1) make a query to return an object container your dealers and brands
2) make an array with depth that the select will display as a tree :
here is an example to illustrate :
->add('contact', 'entity', array(
'class' => 'AcmeBundle:Entity'
'query_builder' => function ( \acme\bundle\Entity\entityRepository $c){
$dealers = $c->yourFunctionThatReturnesDealers('d');
$dealersGroupedByBrands = array();
foreach ( $dealers as $dealer) {
foreach ($dealers->getBrands() as $brand) {
$dealersGroupedByBrands[$brand->getName()][] = $dealer;
}
return $dealersGroupedByBrands;
},
'attr' => array( 'class' => 'other')
))
;
}
pretty cool , no ?

Categories