Symfony2 another Entity in Formbuilder - symfony

i got a problem / understanding problem.
I have 3 db tables
user, usergroup and user2usergroup
the foreign fields:
user.id => user2usergroup.user_id
user2usergroup.group_id => usergroup.id
So now i want to generate and edit my user object and get the relationship to my formbuilder.
I tried some ideas and researched for help.
Not this is the acutal state:
controller:
/**
* creates user form
* #param users $entity
* #param string $sUrl
* #return Form
*/
public function createUserForm(users $entity, $sUrl){
$form = $this->createForm(new userType(), $entity, array(
'action' => $this->generateUrl($sUrl),
'method' => 'POST',
'groups' => $this->getUserGroups()
));
return $form;
}
formbuilder
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options){
$groups = $options['groups'];
$builder
->add('enabled','checkbox',array(
'required' => false))
->add('locked','checkbox',array(
'required' => false))
->add('username','text',array(
'required' => true))
->add('email','email',array(
'required' => true))
->add('password','password',array(
'required' => true))
->add('roles', 'choice', array(
'choices' => array('ROLE_ADMIN' => 'Admin', 'ROLE_USER' => 'Benutzer'),
'required' => true,
'multiple' => true
))
->add('groups','entity',array(
'class' => 'UniteUserBundle:usergroup',
'query_builder' => function(EntityRepository $er){
return $er->findAll();
},
))
->add('save','submit')
;
}
Maybe you can help me to understand
- how to get all groups to the form
- how to edit the user and see which groups he is in (edit view)
- how i can persist the groups i selected in the user form to the user2usergroup
Thanks a lot my friends =)

To show the groups where a user belongs to is not to difficult:
{% for usergroup in entity.getUserGroups() %}
<li>{{ usergroup.name }}</li>
{% endfor %}
To edit them in a USER form is more complicated since you are editing one user but this user can be in zero, one or multiple groups. That means that each group in the list could be edited and deleted and that you can add a new group.
For this situation you can use the Collection field type. In this article you can read step by step how to use it.

Related

unable to transform value for property path "tagname". Expected a Doctrine\Common\Collections\Collection object

I am working with two ManyToMany related entities, namely category and tag.
The entity Tag(relevant details):
/**
*
* #var string
*
* #ORM\Column(name="tagname", type="string")
*/
protected $tagname;
/**
* #ORM\ManyToMany(targetEntity="Category", mappedBy="tags")
*/
protected $categories;
The entity Category(relevant details):
/**
*
* #var string
*
* #ORM\Column(name="CategoryName", type="string",length=200)
*/
protected $categoryname;
/**
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="categories")
*/
protected $tags;
I have a form with a select-input(CategoryType) and a multiple select-input(TagType) fields. Both the fields are EntityType fields. The TagType is embedded inside the CatgoryType.
For this I am not able to utilise the cascade=persist functionality and am adding the submitted tags manually inside my controller. On submission the form data gets persisted in the database without any issues.
The problem is, after submission, when I fetch the submitted category(and the associated tags) in my controller, and pass it to the form, I get this error - Unable to transform value for property path "tagname": Expected a Doctrine\Common\Collections\Collection object.
The var_dump result of the fetched category object(var_dump($category->getTags()->getValues());) gives me an array of the associated Tag objects, with the property protected 'tagname' => string 'tag1'.
From what I understand Interface Collection is quite similar to a php array and My guess is that the tagname field expects all the tagnames in an ArrayCollection or Collection object format. I am not sure whether what is the specific difference.
However I am still clueless how do I pass the already persisted category object in my form.
Here are the categoryname and tags field in the CategoryType:
$builder->add('categoryname', EntityType::class, array(
'class' => 'AppBundle:Category',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.id', 'ASC');
},
'choice_label' => 'categoryname',
'expanded' => false,
'multiple' => false,
'label' => 'Choose Category',
));
$builder->add('tags', CollectionType::class, array(
'entry_type' => TagType::class,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
));
Here is the embedded tagname field in the TagType:
$builder->add('tagname', EntityType::class, array(
'class' => 'AppBundle:Tag',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('t')
->orderBy('t.id', 'ASC');
},
'choice_label' => 'tagname',
'expanded' => false,
'multiple' => true,
'label' => 'Choose Tags',
));
Any ideas?
Try to rid off 'multiple' => true in embedded form. This worked for me.
I've had a similar problem where my EntityType form element gave me this error message:
Unable to reverse value for property path 'my_property' Expected an array.
Setting multiple to false would make the implementation useless, so a better option for me was to set the empty_data to an empty array:
"empty_data" => [],
In your case you might be able to solve the problem by setting the empty_data to a Collection, something like this will probably work:
"empty_data" => new ArrayCollection,
Here is how you do it (from source: https://www.youtube.com/watch?v=NNCpj4otnrc):
***And here is a text version of this image #1, the reason I did not add text because it was hard to read when the format is not proper!
<?php
namespace App\Form;
use App\Entity\Post;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class PostType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'attr' => [
'placeholder' => 'Etner the title here',
'class' => 'custom_class'
]
])
->add('description', TextareaType::class, [
'attr' => [
'placeholder' => 'Enter teh description here',
]
])
->add('category', EntityType::class, [
'class' => 'App\Entity\Category'
])
->add('save', SubmitType::class, [
'attr' => [
'class' => 'btn btn-success'
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Post::class,
]);
}
}
***And here is a text version of this image #2, the reason I did not add text because it was hard to read when the format is not proper!
class FormController extends AbstractController
{
/**
* #Route("/form", name="form")
* #param Request $request
* #return Response
*/
public function index(Request $request)
{
$post = new Post(); // exit('this');
/*$post->setTitle('welcome');
$post->setDescription('description here');*/
$form = $this->createForm(PostType::class, $post, [
'action' => $this->generateUrl('form'),
'method' => 'POST'
]);
// handle the request
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
// var_dump($post); die();
// save to database
$em = $this->getDoctrine()->getManager();
$em->persist($post);
$em->flush();
}
return $this->render('form/index.html.twig', [
'postaform' => $form->createView()
]);
}
My assumption was wrong, I need to either make tagname field an array or create a ManyToOne or ManyToMany relationship with any other entity so it can be an arraycollection. Only then it is possible to use tagname as a multiple select field.
/**
*
* #var array
*
* #ORM\Column(name="tagname", type="array")
*/
protected $tagname;
or
/**
* #ORM\ManyToMany(targetEntity="SOME_ENTITY", mappedBy="SOME_PROPERTY")
*/
protected $tagname;
or
/**
* #ORM\ManyToOne(targetEntity="SOME_ENTITY", mappedBy="SOME_PROPERTY")
*/
protected $tagname;
The exception thrown is pretty explicit, and the problem is probably here:
$builder->add('tagname', EntityType::class, array()
According to your Tag entity, Tag::$tagname is not an entity collection, it's a string. You should add the property with
$builder->add('tagname', TextType::class, array()
shouldn't you ?

Dynamic Generation for Submitted Forms with Form events

I've a little problem with FormEvents, I want do 3 fields populated dynamically.
I explain, I've 3 fields: Project > Box > Cell, the user choose a Project, the Box list is updated, he choose a Box, the cell list is updated.
To do it, I use FormEvent like the documentation say (http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-form-events-submitted-data)
But I've a problem, for just one field dynamically updated, it's work, but no for 2 fields... Actually a user can choose a project, and when he does it, the box field is updated. But, when he choose a box, the cell field wasn't updated...
But, I've find something, who permit it to work, just change something in a ->add() and inversed to ->add(). But I don't want it.
My code is:
$builder
->add('project', EntityType::class, array(
'class' => 'AppBundle\Entity\Project',
'choice_label' => 'name',
'placeholder' => '-- select a project --',
'mapped' => false,
))
->add('box', EntityType::class, array(
'class' => 'AppBundle\Entity\Box',
'choice_label' => 'name',
'placeholder' => '-- select a box --',
'choices' => [],
))
->add('cell', ChoiceType::class, array(
'placeholder' => '-- select a cell --',
))
;
And when I change it to:
builder
->add('box', EntityType::class, array(
'class' => 'AppBundle\Entity\Box',
'choice_label' => 'name',
'placeholder' => '-- select a box --',
// 'choices' => [],
))
->add('project', EntityType::class, array(
'class' => 'AppBundle\Entity\Project',
'choice_label' => 'name',
'placeholder' => '-- select a project --',
'mapped' => false,
))
->add('cell', ChoiceType::class, array(
'placeholder' => '-- select a cell --',
))
;
It's work... But I want an empty list for box at the start, and I want project before box...
A little precision, this form is embded in an other form as a CollectionType.
All the code of this Type:
<?php
namespace AppBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TubeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('project', EntityType::class, array(
'class' => 'AppBundle\Entity\Project',
'choice_label' => 'name',
'placeholder' => '-- select a project --',
'mapped' => false,
))
->add('box', EntityType::class, array(
'class' => 'AppBundle\Entity\Box',
'choice_label' => 'name',
'placeholder' => '-- select a box --',
'choices' => [],
))
->add('cell', ChoiceType::class, array(
'placeholder' => '-- select a cell --',
))
;
// MODIFIER
$boxModifier = function (FormInterface $form, $project) {
$boxes = (null === $project) ? [] : $project->getBoxes();
$form->add('box', EntityType::class, array(
'class' => 'AppBundle\Entity\Box',
'choice_label' => 'name',
'placeholder' => '-- select a box --',
'choices' => $boxes,
));
};
$cellModifier = function(FormInterface $form, $box) {
$cells = (null === $box) ? [] : $box->getEmptyCells();
$form->add('cell', ChoiceType::class, array(
'placeholder' => '-- select a cell --',
'choices' => $cells,
));
};
// FORM EVENT LISTENER
$builder->get('project')->addEventListener(
FormEvents::POST_SUBMIT,
function(FormEvent $event) use ($boxModifier) {
$project = $event->getForm()->getData();
$boxModifier($event->getForm()->getParent(), $project);
}
);
$builder->get('box')->addEventListener(
FormEvents::POST_SUBMIT,
function(FormEvent $event) use ($cellModifier) {
$box = $event->getForm()->getData();
$cellModifier($event->getForm()->getParent(), $box);
}
);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Tube'
));
}
}
Thanks a lot to your help :)
You should use $builder->addEventListener. For multiple fields all you need to do is to have dynamic fields inside FormEvents::PRE_SET_DATA event handler. Also, use parent field data, as explained in the doc to fetch child field choices.
I have used this approach, for generating Country, State and City Entities in hierarchical fields. Let me know if it helps or you need more information.
EDIT : For bigger logic, you can use eventSubscriber which will keep your code clean and you also get to re-use dynamic part of the form for somewhere else.
For multiple dependent hierarchical fields, just add them through conditions in the eventSubscriber class.
Update with code snippet :
Here is a walk through on code snippet that worked for me in Symfony 2.7
Note : I don't replace the dynamic html field as described in the document, instead via jQuery I simply collect child options as per selected parent option and fill in those. When submitted, The form recognises correct child options as per eventSubscriber context. So here is how you might do it :
In your parent Form type (where you have all 3 fields) call a eventSubscriber instead of defining those 3 fields :
$builder->add(); // all other fields..
$builder->addEventSubscriber(new DynamicFieldsSubscriber());
Create an eventSubscriber as defined in the doc, here the file name is DynamicFieldsSubscriber
<?php
namespace YourBundle\Form\EventListener;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\FormInterface;
class DynamicFieldsSubscriber implements EventSubscriberInterface
{
/**
* Define the events we need to subscribe
* #return type
*/
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'preSetData', // check preSetData method below
FormEvents::PRE_SUBMIT => 'preSubmitData', // check preSubmitData method below
);
}
/**
* Handling form fields before form renders.
* #param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
$location = $event->getData();
// Location is the main entity which is obviously form's (data_class)
$form = $event->getForm();
$country = "";
$state = "";
$district = "";
if ($location) {
// collect preliminary values for 3 fields.
$country = $location->getCountry();
$state = $location->getState();
$district = $location->getDistrict();
}
// Add country field as its static.
$form->add('country', 'entity', array(
'class' => 'YourBundle:Country',
'label' => 'Select Country',
'empty_value' => ' -- Select Country -- ',
'required' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.status = ?1')
->setParameter(1, 1);
}
));
// Now add all child fields.
$this->addStateField($form, $country);
$this->addDistrictField($form, $state);
}
/**
* Handling Form fields before form submits.
* #param FormEvent $event
*/
public function preSubmitData(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
// Here $data will be in array format.
// Add property field if parent entity data is available.
$country = isset($data['country']) ? $data['country'] : $data['country'];
$state = isset($data['state']) ? $data['state'] : $data['state'];
$district = isset($data['district']) ? $data['district'] : $data['district'];
// Call methods to add child fields.
$this->addStateField($form, $country);
$this->addDistrictField($form, $state);
}
/**
* Method to Add State Field. (first dynamic field.)
* #param FormInterface $form
* #param type $country
*/
private function addStateField(FormInterface $form, $country = null)
{
$countryCode = (is_object($country)) ? $country->getCountryCode() : $country;
// $countryCode is dynamic here, collected from the event based data flow.
$form->add('state', 'entity', array(
'class' => 'YourBundle:State',
'label' => 'Select State',
'empty_value' => ' -- Select State -- ',
'required' => true,
'attr' => array('class' => 'state'),
'query_builder' => function (EntityRepository $er) use($countryCode) {
return $er->createQueryBuilder('u')
->where('u.countryCode = :countrycode')
->setParameter('countrycode', $countryCode);
}
));
}
/**
* Method to Add District Field, (second dynamic field)
* #param FormInterface $form
* #param type $state
*/
private function addDistrictField(FormInterface $form, $state = null)
{
$stateCode = (is_object($state)) ? $state->getStatecode() : $state;
// $stateCode is dynamic in here collected from event based data flow.
$form->add('district', 'entity', array(
'class' => 'YourBundle:District',
'label' => 'Select District',
'empty_value' => ' -- Select District -- ',
'required' => true,
'attr' => array('class' => 'district'),
'query_builder' => function (EntityRepository $er) use($stateCode) {
return $er->createQueryBuilder('s')
->where('s.stateCode = :statecode')
->setParameter('statecode', $stateCode);
}
));
}
}
After this, you need to write jQuery events which should update child options on change of parent option explicitly, You shouldn't face any error on submission of the form.
Note : The code above is extracted and changed for publishing here. Take care of namespace and variables where ever required.

Symfony 2 Persist multiple entities in one form

I work on a symfony 2 app and I need some help to resolve a case I never had before.
My app has only one page with multiple blocs. Let's consider animals for a simple example. On my page, I have a first bloc where I show N dogs with all their caracteristics, then a second bloc where I show rabbits, then a third bloc with cats ect.
I also have an admin page where the user may modify the displayed data on the main page. The problem I have is that I must use only one page to admin all the blocs. That means that I have N dog, M rabbits, P cats entities displayed on my admin page and when I submit my form, Symfony must delete, update or insert each entity in database.
To do so, I created an entity and a formType for each animal, and a mapped superclass called Website with arraycollections for each entity.
Website Entity :
/**
* Website
* #ORM\MappedSuperclass()
*/
class Website
{
/**
* #var ArrayCollection
*/
private $dogs;
/**
* #var ArrayCollection
*/
private $rabbits;
/**
* #var ArrayCollection
*/
private $cats;
...
}
Website Type :
class WebsiteType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('dogs', 'collection', array(
'type' => new DogType(),
'allow_add' => true,
'allow_delete' => true
))
->add('rabbits', 'collection', array(
'type' => new RabbitType(),
'allow_add' => true,
'allow_delete' => true
))
->add('cats', 'collection', array(
'type' => new CatType(),
'allow_add' => true,
'allow_delete' => true
))
->add('save', 'submit')
;
}
}
In my controller :
$manager = $this->getDoctrine()->getManager();
$dogs = $manager->getRepository('MyBundle:Dog')->findAll();
$rabbits = $manager->getRepository('MyBundle:Rabbit')->findAll();
$cats = $manager->getRepository('MyBundle:Cat')->findAll();
$website = new Website();
$website->setDogs($dogs);
$website->setRabbits($rabbits);
$website->setCats($cats);
$form = $this->createForm(new WebsiteType(), $website);
if ($form->handleRequest($request)->isValid()) {
$manager->persist($website);
$manager->flush();
}
return $this->render('MyBundle:Default:admin.html.twig', array(
'form' => $form->createView(),
'website' => $website
));
When I submit the form, I have this error :
The given entity of type 'MyBundle\Entity\Website'
(MyBundle\Entity\Website#0000000040d8aeb900007fd77a072110) has no
identity/no id values set. It cannot be added to the identity map.
What should I do ?

symfony2 form: how to save entity and how to add more then one entity to the same form?

I have this function in a entitytype class
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//...other controls
->add('types', 'entity', array(
'class' => 'MyApplicationBundle:Type',
'property' => 'type',
'expanded' => false,
'multiple' => true))
->add('save', 'submit');
}
the archive entity has a type property, many to may relation
/**
* #ORM\ManyToMany(targetEntity="Type", mappedBy="archives")
**/
private $types;
the type entity has a archives property on the other side
/**
* #ORM\ManyToMany(targetEntity="Archive", inversedBy="types")
* #ORM\JoinTable(name="types_archives")
**/
private $archives;
the form is correctly displayed with a select multiple control but I'm only able to save in the archive table, not in the types_archives table. Any idea on how to fix?
also, can I add more then one entity to the same type?
thank you
If just one side of relations saved in database try to do following steps:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//...other controls
->add('types', 'entity', array(
'class' => 'MyApplicationBundle:Type',
// This makes form call setter method on related entity
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
'property' => 'type',
'expanded' => false,
'multiple' => true))
->add('save', 'submit');
}
in Archive entity:
public function addType(Type $type){
$this->types[] = $type;
$type->addArchive($this);
}
public function removeType(Type $type){
$this->types->removeElement($type);
$type->setArchive(null);
}
I hope this helps about first part of your question.
For the second part you can use collection type check out following link:
http://symfony.com/doc/current/reference/forms/types/collection.html
Here are some directions that I would give you.
1. To save the related entities, try reading about "cascade persist" if you are using doctrine for example.
2. To have multiple entities on the form, read about "class composition". A composite object which you will set as the form's "data class" will allow you contain multiple entity objects.

Can't add values to ManyToMany after creating an entity of the JoinTable (1-n-1)

At first I had a ManyToMany relation between the User and Club entity.
Now, I wanted to add a column in the JoinTable (users_clubs) so I had to create a third entity UserClub. I ran the doctrine:schema:update --force command and all seemed to be fine.
I have my ProfileForm setup with an Entity field like this:
->add(
'clubs', 'entity', array(
'class' => 'AcmeMainBundle:Club',
'property' => 'name',
'multiple' => 'true',
'query_builder' => function(EntityRepository $er)
{
return $er->createQueryBuilder('club')
->orderBy('club.name', 'ASC');
},
)
)
The form renders well but I get this error when saving the form:
Catchable Fatal Error: Argument 1 passed to Acme\UserBundle\Entity\User::addClub() must be an instance of Acme\MainBundle\Entity\UserClub, instance of Acme\MainBundle\Entity\Club given in /src/Acme/UserBundle/Entity/User.php
User entity addClub function:
/**
* Add clubs
*
* #param UserClub $userClub
* #return User
*/
public function addClub(UserClub $userClub)
{
if (!$this->userClubs->contains($userClub))
{
$this->userClubs->add($userClub);
$userClub->setUser($this);
}
return $this;
}
->add(
'clubs', 'entity', array(
'class' => 'AcmeMainBundle:Club',
'property' => 'name',
'multiple' => 'true',
'query_builder' => function(EntityRepository $er)
{
return $er->createQueryBuilder('club')
->orderBy('club.name', 'ASC');
},
)
)
Can you post your user Entity please ?
Does your user club are nammed user_clubs in your User entity ?
If yes :
->add(
'userclubs', 'entity', array(....
not :
->add(
'clubs', 'entity', array(
I solved it by following this guide:
http://www.prowebdev.us/2012/07/symfnoy2-many-to-many-relation-with.html
I changed my addClub function and added a setClubs function which takes care of setting the club and user in the new UserClub entity.

Resources