Symfony form many-to-many without collection - php

I have 2 entities, User and Account. An Account can be own by multiple users and a User can own multiple Account. So I have this entity UserHasAccount that is handling this relation, plus some few fields that have personalised values for each User - Account relation, such as position, showInMenu, showInSideBar.
In the settings of the account, when a user is logged in I want to display those options but only for the current logged user.
So I end up with 2 form type:
AccountEditType:
class AccountEditType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', null, array(
'label' => 'account.form.name',
'translation_domain' => 'AcmeAccountBundle',
))
->add('timezone', 'genemu_jqueryselect2_timezone', array(
'label' => 'account.form.timezone',
'translation_domain' => 'AcmeAccountBundle',
))
->add('accountUser', new UserAccountEditType($options['user']))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\AccountBundle\Entity\Account',
'user' => null,
'validation_groups' => array('AcmeUpdateAccount'),
'cascade_validation' => true,
));
}
public function getName()
{
return 'acme_account_edit_settings';
}
}
UserAccountEditType:
class UserAccountEditType extends AbstractType
{
protected $user;
public function __construct($user)
{
$this->user = $user;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('showInMenu', null, array(
'label' => 'account.user_account.form.show_in_menu',
'translation_domain' => 'AcmeUserBundle',
'required' => false,
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\UserBundle\Entity\UserHasAccount',
));
}
public function getName()
{
return 'acme_general_account_user';
}
}
The problem is that Account.accountUser is a collection of user and so I cannot use UserAccountEditType which is mapping only one relation (not the collection). If I leave it like this I have the following error:
The form's view data is expected to be an instance of class Acme\UserBundle\Entity\UserHasAccount, but is an instance of class Doctrine\ORM\PersistentCollection
How can I possibly select one of the relation in the form type?
Cheers,
Maxime

Related

Symfony: How to disable input TextType for name attribute inside CollectionType with own condition

(Symfony 5.2, doctrine, PHP 8.0)
Take a look at this sample code:
more described here: https://symfony.com/doc/current/form/form_collections.html
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('description');
$builder->add('tags', CollectionType::class, [
'entry_type' => TagType::class,
'entry_options' => ['label' => false],
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Task::class,
]);
}
}
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('name');
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$tag = $event->getData();
$form = $event->getForm();
dd($tag); // empty because of prototype: true (as I said need it for adding new t
});
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Tag::class,
]);
}
}
TABLES:
TASK: id, name
TAG: id, task_id, name
my problem:
I would like to set - HTML attribute disabled for input name that meets the condition.
I need to get Task ID inside TagType so I can check if this id is used in another table and if so - disable changing tag name.
I tried multiple things but nothing worked for me.
The main problem is its a prototype (need it for adding/removing) and I don't receive tag id inside $tag = $event->getData(); in TagType so I can't get task id.
any idea? I didn't found the same problem on the internet and I'm not able to figure it out alone.
Something you can try is to use the PRE_SET_DATA event on the task form side to get the ID, and pass that ID to your tag form with the entry_options. Something like (not tested, but you'll get the idea) :
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('description');
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$task = $event->getData();
$form = $event->getForm();
$form->add('tags', CollectionType::class, [
'entry_type' => TagType::class,
'entry_options' => [
'label' => false,
'task_id' => !empty($task) ? $task->getId() : null,
],
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
]);
});
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Task::class,
]);
}
}
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// $options['task_id']
// Use your taskId here. The value can be null, on the task creation form.
$builder->add('name');
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Tag::class,
'task_id' => null,
]);
}
}

Symfony3 pass form data to collection inside collection

I have some nested forms with CollectionType and seems that the data from the constructor is not passed to the 2nd nesting level.
I simplified my form classes, just with I think's important (if you want more info jut tell me in the comments).
The bottom level form is entirely generated depending on the Activity entity class:
class ActivityServiceCreationType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) use ($options, $router) {
$activity = $event->getData();
dump($activity); //JUST TO TEST
$form = $event->getForm();
... //$form->add of all necessary fields
}
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Activity',
...
);
}
}
Over the ActivityServiceCreationType I have the next form that is just a collection of the previous one:
class ActivityServiceCreationMultipleType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('activities', CustomCollectionType::class, [
'entry_type' => ActivityServiceCreationType::class,
'entry_options' => $options,
'mapped' => true,
'allow_add' => true,
'show_add_link' => true,
])
;
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) use ($options) {
$data = $event->getData();
dump($data); //To test the data arriving to this form
});
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => null,
...
));
}
}
Then I have the "main" form, with I create from the controller:
class ActivityServiceCreationCollectionType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('selectAll', CheckboxType::class, [...])
...
;
$builder->add('multipleActivities', CustomCollectionType::class, [
'entry_type' => ActivityServiceCreationMultipleType::class,
'entry_options' => [
"router" => $options["router"],
"em" => $options['em'],
"basePeriod" => $options['basePeriod'],
'fit' => $options['fit'],
'periods' => $options['periods'],
'activities' => $options['activities']
],
'mapped' => true
])
;
}
From the controller I want to set the Activity objects to the ActivityServiceCreationType form, so the fields can be created. And I'm doing it like this:
$form = $this->createForm(ActivityServiceCreationCollectionType::class,
["multipleActivities" => ["activities" => $activities]],
[
"router" => $this->get("router"),
"em" => $this->getEm(),
"periods" => $periods,
"basePeriod" => $basePeriod,
'fit' => $fit
]);
As you can see the data for the form is:
["multipleActivities" => ["activities" => $activities]]
The results for the dumps that I put in the code is the following:
For the first dump, in the ActivityServiceCreationMultipleType I get an ArrayCollection of Activities
witch is what is expected, no problem here,
But in the second dump, in the ActivityServiceCreationType, I'm getting null. Here what I expected is an Activity entity for each one of the forms in the collection, right?
Can you tell me where I'm wrong?
--
Edited to add more info:
I've been trying to know when the data is "lost" and added some code to the event in the collection type.
In the ActivityServiceCreationMultipleType changed the POST_SET_DATA this way:
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) use ($options) {
$data = $event->getData();
dump($data);
$form = $event->getForm();
$form->get('activities')->setData($data);
});
Now the dump() in the ActivityServiceCreationMultipleType (that you see in the last code snippet) shows the array of activities. Directly the activities.
And the dump() in ActivityServiceCreationType is executed 36 times (one for each activity) with null...
Don't know why it seems to be passing the data to the last embedded form, but the event can not get it.
EDIT
The configurateOptions of ActivityServiceCreationCollectionType must be:
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => null,
'multipleActivities' => null
);
}
The configureOptions of ActivityServiceCreationMultipleType.
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => null,
'entry_options' => null,
'router" => null,
'em" => null,
'basePeriod" => null,
'fit' => null,
'periods'=>null,
'activities' => null
));
}
The configureOptions of ActivityServiceCreationType.
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Activity',
'entry_options' => null,
'router" => null,
'em" => null,
'basePeriod" => null,
'fit' => null,
'periods'=>null,
'activities' => null
));
}
In conclusion, you must always indicate every external property that you want to pass to the form in configureOptions

Embedding nested form type fields in empty symfony form

I have form type for a Shop entity. This links in a 1-1 relationship to a ShopAddress entity.
When I nest the ShopAddress entity it appears blank when creating a new shop. How can I get this to render with the relevant blank fields when creating a new Shop?
// App/Froms/ShopType.php
class ShopType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add( "name" ) // rendering fine
->add( "shopAddress", CollectionType::class, [
"entry_type" => ShopAddressType::class, // no visible fields
] )
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
"data_class" => Shop::class,
));
}
}
// App/Froms/ShopAddressType.php
class ShopAddressType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("addressLine1", TextType::class ) // not rendering
->add("addressLine2") // not rendering
->add("postcode") // not rendering
->add("county"); // not rendering
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
"data_class" => ShopAddress::class,
));
}
}
Yep. Solved it. Docs had the answer You need to add it as a new FormBuilderInterface object in the add() method:
class ShopType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", TextType::class)
->add(
// nested form here
$builder->create(
'shopAddress',
ShopAddressType::class,
array('by_reference' => true ) // adds ORM capabilities
)
)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
"data_class" => Shop::class,
));
}
}
Adding array('by_reference' => true ) will allow you to use the full ORM (Doctrine in my case) capabilities (e.g. $shop->getAddress()->setAddressLine1('this string to add')).

form type mapped false symfony2

I have a trouble with a formtype with mapped=false.
In controller, I called the form with:
$form = $this->createForm(new JurisdictionUserNewType(), $jurisdiction_user);
This is my JurisdictionUserNewType:
class JurisdictionUserNewType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new CapitalLetterToLowerCaseTransformer();
$builder
->add('name', 'text')
->add($builder->create('email', 'email')
->addModelTransformer($transformer))
->add('securityUser', new SecurityUserType(), array('mapped' => false))
->add('save', 'submit');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Radmas\Open010Bundle\Document\JurisdictionUser'
));
}
public function getName()
{
return 'jurisdictionUserNew';
}
}
This is my SecurityUserType :
class SecurityUserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('first_name', null, ['label' => 'profile.edit.labels.first_name', 'icon_class' => 'fa fa-user'])
->add('last_name', null, ['label' => 'profile.edit.labels.last_name', 'icon_class' => 'fa fa-user'])
->add('nickname', null, ['label' => 'profile.edit.labels.nickname',
'attr' => [ 'help_text' => 'profile.edit.labels.nickname_help'], 'icon_class' => 'fa fa-globe']);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Radmas\SecurityBundle\Document\SecurityUser'
));
}
public function getName()
{
return 'securityUser';
}
}
When I put data in the form, I get the object jurisdictionUser in the modalview but I dont get object securityUser.
If you set 'mapped' => false on any field, you're saying that that field isn't related to your entity, so you don't get it when you retrieve the entity from the submitted form.
You can get it anyway as a single field from the form, as:
$form->handleRequest($request);
if ($form->isValid()) {
$entity = $form->getData();
$securityUser = $form->get('securityUser')->getData();
}

Symfony form collection

I'm trying to add a formType in a other form type.
CustomerType:
class CustomerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('firstname', 'text', array(
'required' => 'required'
))
->add('middlename')
->add('lastname')
->add('email', 'email')
->add('groups', 'entity', array(
'class' => 'MV\CMSBundle\Entity\Group',
'property' => 'name',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('g')
->orderBy('g.name', 'ASC');
}
))
->add('profile', 'collection', array(
'type' => new ProfileType()
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MV\CMSBundle\Entity\User',
));
}
public function getName()
{
return 'customer';
}
}
ProfileType:
class ProfileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('isActive', 'checkbox', array(
'required' => false
))
->add('phone')
->add('address')
->add('city')
->add('zipcode')
->add('country')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MV\NameBundle\Entity\Profile',
));
}
public function getName()
{
return 'profile';
}
}
Then I get this message:
Expected argument of type "array or (\Traversable and \ArrayAccess)", "MV\NameBundle\Entity\Profile" given.
If I comment that line i get: (just to check what will goes wrong ;) )
The form's view data is expected to be of type scalar, array or an instance of \ArrayAccess, but is an instance of class MV\NameBundle\Entity\Profile. You can avoid this error by setting the "data_class" option to "MV\NameBundle\Entity\Profile" or by adding a view transformer that transforms an instance of class MV\NameBundle\Entity\Profile to scalar, array or an instance of \ArrayAccess.
What do I do wrong?
Symfony2: 2.1.8-DEV
It's not clear what your mapping is between your Customer entity and your Profile entity, but I guess there are 2 options:
Option 1: You have a OneToOne relationship (One customer can only have One profile). Therefore, you don't need the collection at all but simply need to embed a single object.
->add('profile', new ProfileType());
Option 2: You want a ManyToOne relationship (One customer can have many profiles). In that case, you need to use to embed a collection of forms. If this is what you want, rename $profile to $profiles in your Customer entity, and do the following:
//MV\CMSBundle\Entity\Customer
use Doctrine\Common\Collections\ArrayCollection;
/**
* Your mapping here.
*
* #ORM\ManyToOne(targetEntity="MV\NameBundle\Entity\Profile")
*/
protected $profiles;
public function __construct()
{
//This is why you had an error, this is missing from your entity
//An object was sent instead of a collection.
$this->profiles = new ArrayCollection();
}
Finally, update your database.

Categories