Generate dynamic amount of CollectionType - php

I want to create a few CollectionType fields.
I have moderators, I load all moderators from db and want to assign sub-users to each modeator in form
Try to do something like that:
public function buildForm(FormBuilderInterface $builder, array $options)
{
foreach ($options['moderators'] as $mod) {
$builder
->add('users['.$mod->getId().']', CollectionType::class, array(
'entry_type' => UserFields::class,
'allow_add' => true,
'label' => false,
'entry_options' => array(
),
));
}
}
In $options['moderators'] there is array of moderators entities.
I want to create the same amount of CollectionType fields as moderators entities (example above - does not work because I cant pass [] as field name)
Each moderator have unique id, so I can use it and pass this id to each CollectionType but how?
How to get back this id of CollectionType in Controller function?
How to display this form on page?

I think you need to dive a little deeper into what collections are ;)
Your collection is invalid, because the entry_type should be a formType. I'm guessing in your case this should be EntityType (if you want the user to choose from a list of users).
So assuming you want a Collection of moderators with each moderator a Collection of sub-users (btw you'd normally make a CRUD and edit each moderator separately, so you don't need a collection with a sub-collection), you'd need something like this:
ModeratorCollectionType.php
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
class ModeratorCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('moderators', CollectionType::class, [
'entry_type' => ModeratorType::class,
'prototype' => true,
'allow_add' => true
]);
}
}
ModeratorType.php
<?php
namespace AppBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ModeratorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('users', CollectionType::class, [
'entry_type' => EntityType::class,
'entry_options' => [
'class' => 'AppBundle:User',
],
'prototype' => true,
'allow_add' => true
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\Moderator',
]);
}
}
In your Controller you'd make a form with the ModeratorCollection
$form = $this->createForm(ModeratorCollectionType::class, $data = []);

Related

How to show a different value for a field in a Symfony FormType

I have a Symfony FormType like this
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class MyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, [
'label' => 'Name',
'required' => true,
'disabled' => false,
]);
}
}
when editing, this text type will be populated with a value from the DB. I would like to change the value in the field depending on some other condition, I tried this with no luck:
if($someValue !== null) {
$builder
->get('name')->setData($someValue);
}
how can I show a different value in the form?
In your controller where you render the form, try to do in this way:
my exemple here is a form for entity named category
$form = $this->createForm(CategoryType::class, $category);
if($someValue !== null) {
$form->get('name')->setData($someValue);
}

CollectionType with mixed entry_type

In Symfony 3 / PHP 7, I need a form who accept an array of mixed type (string, int and array). "entry_type" param for CollectionType only accept a unique Type. How can I have a mixed type ?
$builder
->add('value', CollectionType::class, array(
'entry_type' => ?,
'allow_add' => true,
'allow_delete' => true,
));
You could use embed forms. In your custom form type you can define multiple input fields for your need and than use it in your CollectionType.
// src/Form/TagType.php
namespace App\Form;
use App\Entity\Tag;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('string_field')
->add('int_field')
->add('whatever_field');
}
}
Use in as entry_type like this:
use App\Form\TagType;
// ...
$builder
->add('multiple_fields_collection', CollectionType::class, [
'entry_type' => TagType::class,
'allow_add' => true,
'allow_delete' => true,
]);
More information:
https://symfony.com/doc/current/form/form_collections.html

How to render the input with VichUploaderBundle?

I've a trouble with the bundle VichUploaderBundle with Symfony 4.
I've follow a tutorial to make an upload with VichUploaderBundle to upload multiple files.
My problem is that the input form to upload the file is never render. But I don't see what it's wrong with my Type file.
In my MemberType who can find all fiels, I've
...
->add('member_files', CollectionType::class, [
'label' => 'Add a file',
'entry_type' => MemberFilesType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true
])
...
And my MemberFilesType is :
<?php
namespace App\Form;
use App\Entity\MemberFiles;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichImageType;
class MemberFilesType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('imageFile', VichImageType::class, [
'required' => false,
'download_uri' => true,
'image_uri' => true
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => MemberFiles::class,
]);
}
}
Do you have an idea ?
If you are working with a Member with no MemberFiles yet (empty collection) then that is what is expected. There are no collection entries because there are none persisted, try adding a new MemberFiles to the Member before you create the form.
In your controller action:
if(empty($member->getMemberFiles())){
$member->addMemberFile(new MemberFiles());
}
$form = $this->createForm(MemberType::class, $member);

Accessing User Entity From Form Type in Symfony

I'm trying to build multi-tenancy support in Symfony, doing this I created a group_id column in my User, on top of that, all entities also have this same column. That way users only have access to their group's data.
I've managed to cut through this whole thing like butter with data access and display, but then came the challenge of EntityTypes for the Symfony Forms.
The question is basically, how do I make the EntityType display only the data that that particular group has inputted. Sorting it by the group_id that both the user and contact has. Whats the best way to maybe pass this in so the user only has access to their data?
<?php
namespace ContactBundle\Form;
use ContactBundle\Entity\Contact;
use ContactBundle\Entity\Organization;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('first_name', TextType::Class, [
'attr' => ['class'=>'u-full-width'],
'label' => 'First Name',])
->add('last_name', TextType::Class, [
'attr' => ['class'=>'u-full-width'],
'label'=>'Last Name',
])
->add('email', TextType::Class, [
'attr' => ['class'=>'u-full-width'],
'label'=>'Email Address'
])
->add('organization_id', EntityType::Class, [
'attr' => ['class'=>'u-full-width'],
'required' => false,
'class'=>'ContactBundle:Organization',
'choice_label'=>'name',
'choice_value'=>'id',
'label'=>'Organization'
])
->add('phone', TextType::Class, [
'attr' => ['class'=>'u-full-width'],
'label'=>'Phone',
])
->add('role', TextType::Class, [
'attr' => ['class'=>'u-full-width'],
'label'=>'Role',
])
->add('submit', SubmitType::class, [
'label'=>'Submit',
'attr' => [
'class'=>'button-primary',
'style'=>'margin-top:30px;',]]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class'=>Contact::class,]);
}
}
?>
It is worth noting that I am using FOSUserBundle.
In Symfony it's very easy to inject whatever you need where you need it.
// ...
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class ContactType extends AbstractType
{
private $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('organization_id', EntityType::Class, [
'attr' => ['class'=>'u-full-width'],
'required' => false,
'class'=>'ContactBundle:Organization',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->where('u.group', '?0')
->setParameters([$this->user->getGroup()]);
},
'choice_label'=>'name',
'choice_value'=>'id',
'label'=>'Organization'
])
// ...
If you are not using autowire and autoconfigure (Symfony 3.3+), register your form as a service manually and tag it with form.type:
# config/services.yaml
services:
AppBundle\Form\ContactType:
arguments: ['#security.token_storage']
tags: [form.type]
Related reads
https://symfony.com/doc/current/form/form_dependencies.html#define-your-form-as-a-service
https://symfony.com/doc/current/reference/forms/types/entity.html#using-a-custom-query-for-the-entities
In controller when creating form
$form = $this->createForm(ContactType::class, $contact, ['group_id' => $groupId]);
In form configureOptions method
$resolver->setDefaults(['data_class'=>Contact::class, 'group_id' => null]);
In form buildForm you can get group_id by
$options['group_id']
I got it!
So what I ended up doing was getting rid of the whole idea of using a EntityType on the form side.
I then called the data from the controller and passed it in as an option.
Like so:
$contactManager = $this->get('contact.contact_manager');
$contact = new Contact($contactManager->nextId($group = $this->getUser()->getGroupId()), $group);
$form = $this->createForm(ContactType::class, $contact, ['organizations' => $this->get('contact.organization_manager')->findDataCollection($this->getUser()->getGroupId())]);
$form->handleRequest($request);
Turned my EntityType into a choice type and passed the array of organizations into the 'choices' field as an option. Kept everything else the same like so:
->add('organization_id', ChoiceType::Class, [
'attr' => ['class'=>'u-full-width'],
'required' => false,
'choices'=> $options['organizations'],
'choice_label'=>'name',
'choice_value'=>'id',
'label'=>'Organization'
])
Then, of course, set it within the options so it can expect a variable.
$resolver->setDefaults(['data_class'=>Contact::class, 'organizations' => null]);
I appreciate all of the help and ideas!! :)
You could do this in few different ways, one was to use combination of JavaScript and EntityType.
Basically just load all the entities and hide unwanted entities with JS depending on the previous list's selection. You can use data attribute to specify things needed to make that work.

How to allow admin to add extra unmapped field to a form in real time (Symfony3)

I have a Symfony3 project which facilitates multiple admin/tenants to create custom forms for their users. TenantData is the main entity which contains Salutation, firstname and lastname fields. Based on this entity the tenant/admin can create a form TenantDataType. Here is what TenantDataType looks like:
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Entity\TenantData;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class TenantDataType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('firstname', TextType::class, array());
$builder->add('lastname', TextType::class, array());
$builder->add('title', ChoiceType::class, array(
'choices' => array(
'Ms' => true,
'Mr' => false,
),
));
$builder->add('save', SubmitType::class, array('label' => 'Submit'));
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => TenantData::class,
'csrf_protection' => true,
'allow_extra_fields' => true
));
}
}
Now I want the admin to be able to add an extra field without adding an extra column in the TenantData entity. One approach I know is to create a MetaData table which contains label, datatype, isrequired and ismultiple fields. Based on this Entity I can create a new form by the name MetaDataType. So that the admin can use this form to create an extra field. This basically means that every record of the MetaData table contains the details of an extra field. The problem is how do I use these records to render the extra fields added by the admin/tenant in real time. Since I am creating a new instance of TenantData at the time I am rendering the form and there is no TenantData id at the time which can fetch the related MetaData rows. What relationship do I need to establish to get the desired functionality.
Any ideas?
let's suppose that your formBuilder as you show above with some modification :
TenantDataType
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Entity\TenantData;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class TenantDataType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('firstname', TextType::class, array());
$builder->add('lastname', TextType::class, array());
if(isset($option['userRole'] && $option['userRole']=="ADMIN_ROLE" ){
$builder->add('your_admin_field', TextType::class, array());
}
$builder->add('title', ChoiceType::class, array(
'choices' => array(
'Ms' => true,
'Mr' => false,
),
));
$builder->add('save', SubmitType::class, array('label' => 'Submit'));
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => TenantData::class,
'csrf_protection' => true,
'allow_extra_fields' => true
));
}
}
let's suppose that a tenant user have : TENANT_ROLE
and admin has : ADMIN_ROLE
Controller
$user_role= // get the user role here . (TENANT_ROLE/ADMIN_ROLE)
$form = $this->createForm(TenantDataType ::class, $tenantObject, array(
'userRole' => $user_role,
);
if you want to access $options array from addEventListener:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$user_role = $event->getForm()->getConfig()->getOptions()['userRole'];
}

Categories