Symfony2 multiple embedded forms one to many update foreign key - php

Hello i have a problem with my multiple embedded form (one to many).
One game have many prizes and one prize have many options. When i attemp to save this form get an error message
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'fk_prize' cannot be null
I have already set in my entity on cascade and in my form type set by_refference false but didnt work. All the other assigned foreign keys works perfectly.
UPDATED :
In controller when i do this form saved successfull. But i want to do this with doctrine. Is it bug in doctrine or something wrong in my code ?
Thanks for your time!
//Hacked code in controller to save the form
$prizes = $data->getPrizes();
foreach ($prizes as $prize) {
$prizeOptions = $prize->getPrizesOptions();
foreach ($prizeOptions as $prizeOption) {
$prizeOption->setPrize($prize);
}
}
$em->persist($data);
$em->flush();
<?php
class Game
{
/**
* #ORM\OneToMany(targetEntity="Prize", mappedBy="game", cascade={"persist"})
*/
protected $prizes;
public function __construct()
{
$this->gameUsers = new ArrayCollection();
$this->prizes = new ArrayCollection();
}
}
?>
<?php
class GameType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('alias', 'text' , [
'label'=>'Name'
])
->add('prizes' ,'collection', array(
'type' => new PrizeType($this->intention),
'allow_add' => true,
'allow_delete' => false,
'prototype' => true,
'by_reference' => false,
'label' => false,
))
->add('save', 'submit', [
'attr' => [
'class' => 'btn btn-primary'
]
]);
}
}
<?php
class Prize
{
/**
* The Game
* #ORM\ManyToOne(targetEntity="Game")
* #ORM\JoinColumn(name="game_id", referencedColumnName="id")
*/
protected $game;
/**
* #ORM\OneToMany(targetEntity="PrizeOptions", mappedBy="prize", cascade={"persist"})
*/
protected $prizes_options;
/**
* Constructor
*/
public function __construct()
{
$this->prizes_options = new \Doctrine\Common\Collections\ArrayCollection();
}
}
class PrizeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('prizes_options' ,'collection', array(
'type' => new PrizeOptionsType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => false,
))
;
}
}
<?php
class PrizeOptionsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text' , [
'label'=>'Value'
])
;
}
}

Doctrine can handle changes in entities only on "owning" side. That means, you can modify relations only in entity where is defined join column/join table.
http://doctrine-orm.readthedocs.org/en/latest/reference/unitofwork-associations.html

Related

Problem with embed on a collection of forms of Symfony 4.4

InvalidPropertyPathException
If I submit the form, without completing the data. I get this error.
Could not parse property path "children[upcomingTours].children[[0]].children[address].children[street].data". Unexpected token "]" at position 36.
Some questions :
Is this a symfony bug? If so, is it already fixed in version
5.x?
Has anyone ever encountered this error?
Does anyone have a solution for this problem?
Thank you in advance for your reply
This is the hierarchy of my form
- TourType (custom form)
- UcomingTour (CollectionType)
- AddressType (custom form)
HTLM
<div>
<label for="tour_upcomingTours_0_address_street">Adresse</label>
<input type="text"
id="tour_upcomingTours_0_address_street"
name="tour[upcomingTours][0][address][street]"
placeholder="Tapez un nom de rue">
</div>
TourType
class TourType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
......
->add('upcomingTours', CollectionType::class, [
'label' => false,
'entry_type' => UpcomingTourType::class,
'allow_delete' => true,
'allow_add' => true,
'by_reference' => false,
])
;
}
}
UpcomingTourType
class UpcomingTourType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
.........
->add('address', AddressType::class, [
'label' => false,
])
}
}
AddressType
class AddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
............
->add('street', null, [
'required' => false,
'label' => 'admin.address_line',
'attr' => [ 'placeholder' => 'admin.placeholder_line'],
'constraints' => [
new NotNull([
'message' => 'project.street_not_blank',
])
]
])
;
}
}
TourController
class TourController extends AbstractController
{
/**
* #Route("/tour/new/", name="dashboard_tour_new", methods={"GET","POST"},
* defaults={"_locale" = "%locale%"},
* requirements={"_locale" = "%app_locales%"})
*/
public function new(Request $request): Response
{
$tour = new Tour();
$upcomingTour = new UpcomingTour();
$tour->getUpcomingTours()->add($upcomingTour);
$form = $this->createForm(TourType::class, $tour);
$form->handleRequest($request);
...
}
}
I have found the solution.
Problem
The origin of the problem is the validation of the constraints on the form.
Symfony collectionType encounters a problem when trying to map the Address form
Solution
The solution is the following:
I remove the valuation of the contrants in the form and put it in the entity.
I delete this code:
'constraints' => [
new NotNull([
'message' => 'project.street_not_blank',
])
]
AddressType
class AddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
............
->add('street', null, [
'required' => false,
'label' => 'admin.address_line',
'attr' => [ 'placeholder' => 'admin.placeholder_line'],
])
;
}
}
Then I put it in the entity
Entity Address
class Address extends BaseEntity
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\Column(type="string", nullable=true)
* #Assert\NotBlank(message="project.street_not_blank")
*/
private $street;
}
Conclusion
I think it's a symfony bug, for me it will have to support the two
method
try to delete the following lines from the controller
$upcomingTour = new UpcomingTour();
$tour->getUpcomingTours()->add($upcomingTour);
or wrap these lines in if ($request->isMethod(Request::METHOD_GET))

setting 'by reference' to false, on symfony form throws 'Could not determine access type'

I'm trying to create a form for the creation of a product in sylius. I want to add a collection of "PackItem".
However,only the last item is added and when I add "by_reference" => false I've got this issue
Could not determine access type for property "products".
This is my code
#ProductTypeExtension.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** #var PackItem $packItem */
$packItem = new PackItem();
$packItem->setParent($builder->getData());
$builder
->add('products', CollectionType::class, [
'entry_type' => PackItemType::class,
'allow_add' => true,
'allow_delete' => true,
'entry_options' => [
'data' => $packItem
],
'by_reference' => false,
]);
}
/**
* {#inheritdoc}
*/
public function getExtendedType()
{
return ProductType::class;
}
PackItemType.php
#PackItemType.php
final class PackItemType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('child', 'entity', [
'label' => 'winzana.ui.token',
'class' => Product::class,
'query_builder' => function(EntityRepository $er) {
$qr = $er->createQueryBuilder('t')
->leftJoin('t.products', 'p')
->having('COUNT(p.parent) = 0')
->groupBy('t.id')
->orderBy('t.code', 'ASC')
;
return $qr;
}
])
->add('quantity', IntegerType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => PackItem::class
]);
}
Product :
class Product extends BaseProduct
{
/**
* #ORM\OneToMany(targetEntity="XXXX\PackBundle\Entity\PackItem", mappedBy="parent", cascade={"persist"})
* #var ArrayCollection|PackItem $products
*/
private $products;
Thank you for your time
You can try to initialise your products in the __construct() method of your class product
public function __construct()
{
$this->products= new ArrayCollection();
}
if it does not correct the problem, then check if you set correctly the getProducts(), setProducts() and addProduct().
You can check this page for information,
http://symfony.com/doc/current/best_practices/business-logic.html#doctrine-mapping-information
regards.
The problem was solved by this change
/**
* #param ArrayCollection|PackItem[] $products
*/
public function setProducts($products)
{
$this->products = $products;
}
I don't use the setter so I didn't made it however by_references needs it.
Now I've got an other problem, only the last Item is saved.

Symfony FormType, get Dropdown from Arraycollection

i have my Entity Product with an Arraycollection of featureTypes (ManytoMany)
Class Product:
/**
* #var FeatureType
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\FeatureType", mappedBy="products")
*/
private $featureTypes;
public function __construct()
{
$this->variants = new ArrayCollection();
$this->featureTypes = new ArrayCollection();
}
Class FeatureType:
/**
* #var Product[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Product", inversedBy="featureTypes")
* #ORM\JoinTable(name="products_featureTypes")
*/
private $products;
Now i want to create a form which render me a dropdown of all available featureTypes. I want to select one and submit it.
I tried it like this in my addFeatureTypeToProductType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('featureTypes', EntityType::class, [
'class' => FeatureType::class,
'choice_label' => 'name',
])
->add('submit', SubmitType::class)
->getForm();
}
The output is the Dropdown with all available FeatureTypes. But when I submit the selected featureType, i get an error: "Could not determine access type for property 'featureType'".
Then I tried this way:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('featureTypes', CollectionType::class, [
'entry_type' => FeatureType::class,
'allow_add' => true,
])
->add('submit', SubmitType::class)
->getForm();
}
But this does not work
My Controller:
public function addFeatureTypeAction(Request $request, Product $product)
{
$form = $this->createForm(AddFeatureTypeToProductType::class, $product, [
'action' => $this->generateUrl('admin_products_add_featureTypes', [
'product' => $product->getId()
])
]);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$featureType = $form->get('featureTypes');
$product->addFeatureTypes($featureType);
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->redirectToRoute('admin_products_list_all');
}
return [
'form' => $form->createView()
];
}
Sorry for my english :S
EDIT: Here are my adder/remover and setter/getter:
/**
* #return FeatureType
*/
public function getFeatureTypes()
{
return $this->featureTypes;
}
/**
* #param FeatureType $featureTypes
*/
public function setFeatureTypes($featureTypes)
{
$this->featureTypes = $featureTypes;
}
/**
* Add new FeatureType
*
* #param FeatureType $featureType
*
* #return Product
*/
public function addFeatureTypes($featureType)
{
if (!$this->featureTypes->contains($featureType)) {
$this->featureTypes->add($featureType);
}
return $this;
}
/**
* #param FeatureType $featureType
*
* #return Product
*/
public function removeFeatureTypes($featureType)
{
if ($this->featureTypes->contains($featureType)) {
$this->featureTypes->remove($featureType);
}
return $this;
}
EDIT 2: I tried it again with the first way of my form. But i get a new Error now. I don' know why my entity "FeatureType" don't knows the contains method. It uses the Symfony Arraycollection
Error: Attempted to call an undefined method named "contains" of class "AppBundle\Entity\FeatureType".
Debugging stops in addFeatureTypes($featureType)
Im one step further now. Now, I uses the collectionType.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('featureTypes', CollectionType::class, [
'entry_type' => FeatureTypeType::class,
'allow_add' => true,
])
->add('submit', SubmitType::class)
->getForm();
}
My frontend form shows all featureTypes, which my product already has.
But i don't know, how too add a new one...
The annotation of my properties were wrong.
Class Product:
/**
* #var FeatureType[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\FeatureType", inversedBy="products", cascade={"persist"})
* #ORM\JoinTable(name="products_featureTypes")
*/
private $featureTypes;
Class FeatureType:
/**
* #var Product[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Product", mappedBy="featureTypes", cascade={"persist"})
*
*/
private $products;
My Form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('featureTypeToAdd', EntityType::class, [
'class' => FeatureType::class,
'choice_label' => 'name',
'mapped' => false,
])
->add('submit', SubmitType::class)
->getForm();
}
And my Controller:
public function addFeatureTypeAction(Request $request, Product $product)
{
$form = $this->createForm(AddFeatureTypeToProductType::class, $product, [
'action' => $this->generateUrl('admin_products_add_featureTypes', [
'product' => $product->getId(),
]),
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$featureType = $form->get('featureTypeToAdd')->getData();
$em = $this->getDoctrine()->getManager();
$product->addFeatureTypes($featureType);
$em->persist($product);
$em->flush();
return $this->redirectToRoute('admin_products_list_all');
}
return [
'form' => $form->createView(),
];
}

Symfony CollectionType self referencing

I have many to many association in my entity and i would like to use the collection type field from symfony to add multiple times.
I've never done this way before and i'm kinda lost.
The field that i would like to have multiple times is headquarter with a non mapped field for each headquarter.
The error that i'm getting;
The property "headquarter" in class "AppBundle\Entity\SurveyManager"
can be defined with the methods "addHeadquarter()",
"removeHeadquarter()" but the new value must be an array or an
instance of \Traversable, "AppBundle\Entity\HeadQuarterManager" given.
The Form Type implementing the Collection.
class SurveyOptionType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('headquarter', CollectionType::class, [
'entry_type' => HeadQuarterType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => 'Sediu',
])
->add('isEnabled', CheckboxType::class, [
'label' => 'Chestionar Activ',
'required' => false,
])
->add('submit', SubmitType::class, [
'label' => 'Salveaza',
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => SurveyManager::class
]);
}
}
This is the Collection Form
class HeadQuarterType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('headquarter', EntityType::class, [
'class' => HeadQuarterManager::class,
'label' => 'Sediu',
])
->add('userNumber', TextType::class, [
'label' => 'Numar Utilizatori',
'mapped' => false,
])
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => SurveyManager::class
]);
}
}
UPDATE:
Here is the Entity class stripped away from all the uncesessary data
class SurveyManager
{
/**
* #var int
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\HeadQuarterManager")
* #ORM\JoinTable(name="survey_headquarters",
* joinColumns={#ORM\JoinColumn(name="survey_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="headquarter_id", referencedColumnName="id")}
* )
*/
private $headquarter;
public function __toString()
{
// TODO: Implement __toString() method.
return $this->name;
}
/**
* Constructor
*/
public function __construct()
{
$this->question = new \Doctrine\Common\Collections\ArrayCollection();
$this->headquarter = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add headquarter
*
* #param \AppBundle\Entity\HeadQuarterManager $headquarter
*
* #return SurveyManager
*/
public function addHeadquarter(\AppBundle\Entity\HeadQuarterManager $headquarter)
{
$this->headquarter[] = $headquarter;
return $this;
}
/**
* Remove headquarter
*
* #param \AppBundle\Entity\HeadQuarterManager $headquarter
*/
public function removeHeadquarter(\AppBundle\Entity\HeadQuarterManager $headquarter)
{
$this->headquarter->removeElement($headquarter);
}
/**
* Get headquarter
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getHeadquarter()
{
return $this->headquarter;
}
}
Controller method
$em = $this->getDoctrine()->getManager();
$surveyRepository = $em->getRepository(SurveyManager::class);
//$surveyManager = $surveyRepository->findOneById($surveyId);
$surveyManager = new SurveyManager();
$form = $this->createForm(SurveyOptionType::class, $surveyManager);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($surveyManager);
$em->flush();
return $this->redirectToRoute('admin_survey-manager_survey_options',
[
'id' => $surveyManager->getId()
]);
}
return [
'surveyManager' => $surveyManager,
'form' => $form->createView(),
];
While doing research about this i did a separate demo to test on it to see better the problem.
Here is an updated code simplified that should work really smooth
https://gist.github.com/bogdaniel/a0bcc848e2bd282382f45a2bd15cc0e2
You will find more info about the error in the gist.
I don't really understand what do you want to archive, and what exactly does it mean:
The field that i would like to have multiple times is headquarter with a non mapped field for each headquarter.
But the error is quite self-explaining. Your SurveyManager class has to have some methods to populate data from Form. The flow is following - Form gets data (from User, for example) with an array of headquarters. And then Form looks for the way to pass this array to the object - and doesn't find them because you don't have appropriate methods.
Id doesn't matter if the property is mapped (I believe, you mean mapped to Doctrine), it still has be provided a way to pass data from Form to Object.
How are you rendering your form? I suspect something similar to the issue found here: Error : the new value must be an array or an instance of \Traversable, Entity class given
Other than that, the only issue I can see in your code is that the 'data_class' option from HeadQuarterType should be HeadQuarterManager::class.

Checkbox preselection is not working on EntityType Field (ManyToMany)

My Symfony version is 2.7.3. I have Vehicle entity, which has WorldCountry field (unidirectional ManyToMany):
/**
* Vehicle
* #ORM\Table(name="vehicle")
*/
class Vehicle
{
// ...
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\WorldCountry", fetch="EAGER")
* #ORM\JoinTable(name="vehicle_country_join",
* joinColumns={#ORM\JoinColumn(name="vehicle_id", referencedColumnName="vehicle_id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="country_id", referencedColumnName="country_id")}
* )
*/
protected $countries;
// ...
public function __construct()
{
// ...
$this->countries = new ArrayCollection();
}
}
I have form for vehicle creation/editing:
/**
* Class VehicleType
* #package AppBundle\Form\Vehicle
*/
class VehicleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('countries', 'entity', [
'class' => 'AppBundle:WorldCountry',
'choices' => $this->worldManager->getCountriesByRegionIds([1]),
'choice_label' => 'name',
'expanded' => false,
'multiple' => true,
'label' => false
])
->add('submit', 'submit');
// ...
}
}
Vehicle creation works fine, for example as result I have:
The problem is that on vehicle editing page countries are not preselected (“Kyrgyzstan” in that example), when “choices” option is used:
But without it countries are preselected:
$this->worldManager->getCountriesByRegionIds([1]) - returns array of WorldCountry from repository.
UPDATE
Accidentally I have updated Symfony up to v2.8.7 - now it works as expected :)
P.S. Please let me know if more information is required.

Categories