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.
Related
i have a entity called DynamicForm, which looks like this:
<?php
namespace App\Entity\Product\DynamicForm;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Entity;
/**
* Class DynamicForm
* #package App\Entity\Product
*
* #Entity
* #ORM\Table(name="product_dynamic_form")
*/
class DynamicForm
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(name="id", type="integer", unique=true, nullable=false)
*/
private ?int $id = null;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Product\DynamicForm\Component\Text", mappedBy="dynamicForm")
* #ORM\JoinColumn(name="component_text_id", referencedColumnName="id")
*/
private ?Collection $textComponents;
/**
* #return Collection|null
*/
public function getTextComponents(): ?Collection
{
return $this->textComponents;
}
/**
* #param Collection|null $textComponents
*
* #return DynamicForm
*/
public function setTextComponents(?Collection $textComponents): DynamicForm
{
$this->textComponents = $textComponents;
return $this;
}
}
Also i created a related type for it - DynamicFormType:
<?php
namespace App\Type\Product\DynamicForm;
use App\Entity\Product\DynamicForm\DynamicForm;
use App\Type\Product\DynamicForm\Component\TextType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DynamicFormType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('textComponents', CollectionType::class, [
'entry_type' => TextType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'label' => ' '
])
->add('submit', SubmitType::class, [
'label' => 'form.basic.save'
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('data_class', DynamicForm::class);
}
}
The TextType entry type class from the namespace App\Type\Product\DynamicForm\Component\TextType looks like this:
<?php
namespace App\Type\Product\DynamicForm\Component;
use App\Entity\Product\DynamicForm\Component\Text;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type as FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
class TextType extends AbstractType
{
private TranslatorInterface $translator;
/**
* TextType constructor.
*
* #param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label', FormType\TextType::class, [
'required' => true,
'translation_domain' => 'wedding',
'label' => 'setting.form.dynamic_by_user.query_to_guest'
])
->add('type', FormType\ChoiceType::class, [
'required' => true,
'translation_domain' => 'wedding',
'label' => 'setting.form.dynamic_by_user.select_type',
'attr' => [
'class' => 'enriched',
'data-search-placeholder' => $this->translator->trans('select.search'),
'data-search-no-results-text' => $this->translator->trans('select.search_no_results_found')
],
'choice_translation_domain' => 'wedding',
'choices' => [
'setting.form.dynamic_by_user.type_text' => Text::TYPE_TEXT_FIELD,
'setting.form.dynamic_by_user.type_textarea' => Text::TYPE_TEXT_AREA,
'setting.form.dynamic_by_user.type_email' => Text::TYPE_EMAIL_FIELD,
'setting.form.dynamic_by_user.type_number' => Text::TYPE_NUMBER_FIELD,
]
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('data_class', Text::class);
}
}
While i try to transmit the form, the request in the controller contains the form data as you can see it, in the following screenshot:
My problem is now that i always get the following error (while handling the request for the form via ->handleRequest($request) on created form in controller):
Expected argument of type "?Doctrine\Common\Collections\Collection", "array" given at property path "textComponents".
I have such collection settings also in other classes, but without problems - I don't know any further, can anyone please assist me or see the error?
(I am using Symfony version 5.2.9, if u need any further info just ask for it - I will give it to you as soon as possible)
Trying to add a constructor in your entity
public function __construct()
{
$this->textComponents = new ArrayCollection();
}
Add addTextComponent and removeTextComponent methods intead of setTextComponents
public function addTextComponent(Text $textComponent): self
{
$textComponent->setDynamicForm($this);
$this->textComponents->add($textComponent);
return $this;
}
public function removeTextComponent(Text $textComponent): self
{
$this->textComponents->removeElement($textComponent);
return $this;
}
Add 'by_reference' => false in the textComponents form params
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('textComponents', CollectionType::class, [
'entry_type' => TextType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'label' => ' ',
'by_reference' => false,
])
->add('submit', SubmitType::class, [
'label' => 'form.basic.save'
]);
}
I was able to fix this by doing this, that allows us to pass a Collection or an empty array or an array of your entity which I think is Text:
/**
* #param Collection|Text[] $textComponents
*
* #return DynamicForm
*/
public function setTextComponents($textComponents): DynamicForm
{
$this->textComponents = $textComponents;
return $this;
}
I have two entities, client and order.
I have an admin interface where I show all the orders of a client, where I can modify or delete every order.
To do that I use a Collection Type:
My controller:
$form = $this->createForm(ClientConfigType::class, $client);
This is my ClientConfigType :
<?php
namespace App\Form;
use App\Entity\Client;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ClientConfigType extends AbstractMainType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add("orders",
CollectionType::class,
[
'entry_type' => OrderConfigType::class,
'allow_add' => true,
'label' => false
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => Client::class,
'allow_extra_fields' => true,
));
}
}
And my OrderConfigType is a classic formType.
Everything is working perfectly without any filtering.
But I want to be able to filter and display my collectionType of Order.
For Exemple I would like to display the order of a specific date or the orders > 100$, etc
I tried to use query builder but it's only working for EntityType and not CollectionType
I tried to pass a variable from my Controller to my Form then to my Entity "get" function like that:
$minimumPrice = $request->query->get('minimumPrice');
$form = $this->createForm(ClientConfigType::class, $client, ['minimumPrice' => $minimumPrice ]);
Then in my ConfigType I can retrieve my variable in the configureOptions function but Then, I can't do anything to use that value to filter my collection Type.
How can I filter my collectionType ?
Instead of passing minutePrice you can query like you want your orders, and pass order's collection to the form.
Example here:
class ClientConfigType extends AbstractMainType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add("orders",
CollectionType::class,
[
'entry_type' => OrderConfigType::class,
'allow_add' => true,
'label' => false,
'data' => $options['orderCollection']
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => Client::class,
'orderCollection' => null,
));
}
}
$orderCollection = $em->getRepository(Order::class)->findAll(); //something like this or custom query it s an example
$form = $this->createForm(ClientConfigType::class, $client, ['orderCollection' => $orderCollection ]);
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(),
];
}
I have a Timeslot functionality where the user has to be able to edit the start and end of each slot.
So I would like to create one form that contains alle timeslots. I based my code on this article in symfony docs: http://symfony.com/doc/current/cookbook/form/form_collections.html
The problem is that in this article, there's some kind of parent entity to which the collection instances should be assigned to. In my case, the timeslots are standalone and do not have a relation with another entity (that is relevent at this stage).
So.. my question: How do I create a collection of forms with my timeslots?
Controller.php
/**
* #Route("/settings", name="settings")
* #Template()
*/
public function settingsAction()
{
$timeslots = $this->getDoctrine()->getRepository("ScheduleBundle:Timeslot")->findAll();
$timeslots_form = $this->createFormBuilder()
->add('timeslots', 'collection', array(
'type' => new TimeslotType(),
))
->add('save', 'submit')
->getForm();
return array(
'timeslots' => $timeslots_form->createView()
);
}
TimeslotType.php:
class TimeslotType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('start', 'time', array(
'input' => 'datetime',
'widget' => 'choice',
))
->add('end', 'time', array(
'input' => 'datetime',
'widget' => 'choice',
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Oggi\ScheduleBundle\Entity\Timeslot'
));
}
/**
* #return string
*/
public function getName()
{
return 'timeslot';
}
}
Any thoughts?
thanks in advance!
Pass an array instead of an entity to your form. The form can process either just fine.
$timeslots = $this->getDoctrine()->getRepository("ScheduleBundle:Timeslot")->findAll();
$formData = array('timeslots' => $timeslots);
$timeslots_form = $this->createFormBuilder($formData) ...
I'm working with Symfony2. I have two entities like below:
class User
{
private $idUser;
private $name;
private $country;
...
}
class Country
{
private $idCountry;
private $namecountry;
...
}
And I create a form like this:
class UserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name','text')
->add('country', 'entity', array(
'required' => false,
'label' => 'Country',
'class' => 'TestBundle:Country',
'property' => 'namecountry',
'empty_value' => false,
));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Test\TestBundle\Entity\User'
));
}
/**
* #return string
*/
public function getName()
{
return 'test_Testnbundle_user';
}
}
Everything is okay, but suppose I want to modify a user who:
Lives in Canada
Name is Jack.
My problem occurs whenever the form appears, the field name contains the current value in the database but unlike the country field, it does not contain the correct value in the database. How could this be?
Could you show us the configuration for the relationship that you've defined between User and Country?
What you have should work but only if the relationship is set up correctly between the two entities. Otherwise, it won't match correctly to set the selected value.