Custom Variables in custom FormType - symfony

Got some question about passing custom variables to a custom FormType
I have a custom FormType named KontoType:
I pass some custom variables to it and this works like as expected, if i override the method buildForm and dump the passed $options array, the mandant exists and is a entity.
But how the heck can i now pass this custom variable to the function getChoices() which loads the choices based on the mandant in this custom FormType?
Even if i did reset the $options in the override buildForm function like $options['choices'] = $this->getChoices($options['mandant']) the select box is empty if i render this form.
<?php
namespace App\Form\Type;
use App\Entity\Core\Finanzen\Konto;
use App\Entity\Core\Organisation\Mandant;
use App\Services\LocaleService;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Translation\TranslatorInterface;
class KontoType extends AbstractType
{
/**
* #var ObjectManager
*/
private $manager;
/**
* #var TranslatorInterface
*/
private $translator;
/**
* #var LocaleService
*/
private $localeService;
public function __construct(ObjectManager $manager, TranslatorInterface $translator, LocaleService $localeService)
{
$this->manager = $manager;
$this->translator = $translator;
$this->localeService = $localeService;
}
private function getChoices(Mandant $mandant=null)
{
return $this->manager->getRepository(Konto::class)->findBuchbar(true, $mandant);
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'label' => 'konto.name',
'help' => 'konto.help',
'choices' => null,
'attr' => array(
'class' => 'bs-select',
'aria-hidden' => 'true',
'ref' => 'input',
'multiple' => false,
'tabindex' => 1,
'data-live-search' => true,
'data-size' => 6
),
'choice_label' => function ($choiceValue, $key, $value) {
return $choiceValue->getKonto()." ".$this->localeService->doTranslate($choiceValue);
},
'choice_value' => function(Konto $konto = null) {
return $konto ? $konto->getId() : '' ;
},
'required' => true,
'multiple' => false,
'empty_data' => null,
'label_attr' => array(
'class' => 'control-label'
),
'placeholder' => 'message.bitte wählen',
'mandant' => null
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$options['choices'] = $this->getChoices($options['mandant']);
parent::buildForm($builder, $options); // TODO: Change the autogenerated stub
}
public function getParent() {
return ChoiceType::class;
}
}

Related

symfony easyadmin form field type entity with filter list

I use symfony 3.4 and easycorp/easyadmin-bundle 1.17
This is my class "Quotation", the "artisan" field is an "under registration" of the "Person" entity (person.type = 1) :
class Quotation
{
/** others fields... */
/**
* 1 => 'artisan', 2 => 'customer'
*/
private $type;
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="artisanQuotations", cascade= { "persist" })
* #ORM\JoinColumn(name="artisan_id", referencedColumnName="id")
*/
private $artisan;
/** getters and setters ... */
I have a problem with a form field using a custom field type
form:
fields:
...
- { property: 'artisan', label: '', type: 'AppBundle\Form\Field\ArtisanType' }
I created this form field type to be able to filter the list thanks to the query_builder :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('artisan', EntityType::class, array(
'class' => 'AppBundle:Person',
'label' => false,
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('person')
->where('person.type = 1');
},
'attr' => array('data-widget' => 'select2'),
'multiple' => false,
'expanded'=> false
));
}
my form is displayed perfectly well but when i submit this form i have an error :
Expected argument of type "AppBundle\Entity\Person", "array" given
Thank you in advance for your help
Instead of using the buildForm method you should use configureOptions here. That way your form is not extended by another subform which results in the array.
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ArtisanType extends AbstractType
{
/**
* #param \Symfony\Component\OptionsResolver\OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => 'AppBundle:Person',
'label' => false,
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('person')
->where('person.type = 1');
},
'attr' => array('data-widget' => 'select2'),
'multiple' => false,
'expanded'=> false,
]);
}
/**
* #return string|null
*/
public function getParent()
{
return EntityType::class;
}
}

Add roles field to FOSUserBundle group form

When activating group( base on its documentation) in FOSUserBundle, the group roles are not embedded on edit and update form!I already override GroupFormType and GroupController but I can't pass roles from controller to form class.
my question is how can I add roles to form to let administrator change or assign role to groups?
Solving my problem by adding the role field to override GroupController
public function editAction(Request $request, $groupName)
{
...
/** #var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$formFactory = $this->get('fos_user.group.form.factory');
$form = $formFactory->createForm();
$form->add('roles', 'choice', array(
'choices' => $this->getExistingRoles(),
'data' => $group->getRoles(),
'label' => 'Roles',
'expanded' => true,
'multiple' => true,
'mapped' => true,
));
...
}
public function getExistingRoles()
{
$roleHierarchy = $this->container->getParameter('security.role_hierarchy.roles');
$roles = array_keys($roleHierarchy);
foreach ($roles as $role) {
$theRoles[$role] = $role;
}
return $theRoles;
}
For my part I decided to inerite the GroupFormType class. Here is inherited class :
namespace UserBundle\Form;
use FOS\UserBundle\Form\Type\GroupFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
class GroupType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$permissions = array(
'Utilisateur' => 'ROLE_USER',
'Administrateur' => 'ROLE_ADMIN'
);
$builder
->add('name', null, array('label' => 'form.group_name', 'translation_domain' => 'FOSUserBundle'))
->add('role', ChoiceType::class, array(
'label' => 'Rôle',
'choices' => $permissions,
'multiple' => true,
'expanded' => true
))
;
}
public function getParent()
{
return GroupFormType::class;
}
}
Don't forget to precise your new class in the config.yml
fos_user:
group:
form:
type: UserBundle\Form\GroupType

Use ChoiceList for entity type

I'm trying to use a choice list in a form with the entity type but it's not working if I add data to the form. It's giving me a "could not convert object to int" error.
My buildForm method
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('department', 'entity', array(
'label' => 'Form.department',
'class' => 'HotfloSystemCoreBundle:Department',
'choice_list' => $this->departmentChoiceList,
'multiple' => true,
'required' => false,
'attr' => array(
'class' => 'selectpicker',
'data-actions-box' => true,
'data-live-search' => true,
'data-selected-text-format' => 'count'
),
'horizontal' => false,
));
}
My ChoiceList
class DepartmentChoiceList extends LazyChoiceList
{
/**
* #var EntityManager
*/
protected $em;
public function __construct($em)
{
$this->em = $em;
}
/**
* Loads the choice list
* Should be implemented by child classes.
*
* #return ChoiceListInterface The loaded choice list
*/
protected function loadChoiceList()
{
$departments = $this->getDepartments();
$departmentChoices = [];
foreach ($departments as $department) {
$departmentChoices[$department->getId()] = $department;
}
// Return the choice list
return new SimpleChoiceList($departmentChoices);
}
/**
* Get the departments available in the poli appointment data
*
* #return Department[]
*/
protected function getDepartments()
{
// Get the used department out of the appointment table by using a group by SQL statement
/** #var $qb QueryBuilder */
$qb = $this->em->getRepository('MyBundle:PoliAnalyzeAppointment')
->createQueryBuilder('appointment');
$qb->select('DISTINCT IDENTITY(appointment.department)');
// Get the actual departments
/** #var $qb2 QueryBuilder */
$qb2 = $this->em->getRepository('MyBundle:Department')
->createQueryBuilder('department');
$qb2->where($qb2->expr()->in('department.id', $qb->getDQL()));
$qb2->orderBy('department.name', 'ASC');
return $qb2->getQuery()->getResult();
}
}
I'm using the entity type because it should be converted to an entity and back. If I use the choice type I have to do this myself (which I don't want).
How can I achieve this?
Use query_builder option to filter entity choice list. Something like:
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('a')
->select(array('DISTINCT IDENTITY(a.department)', 'd'))
->from('MyBundle:PoliAnalyzeAppointment', 'a')
->innerJoin('a.department', 'd')
->groupBy('a.department')
->orderBy('d.name', 'ASC');
}
In the buildForm method I would fill it with the standard options array:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('department', 'entity', array(
'label' => 'Form.department',
'choice_list' => $options['departmentChoiceList'],
'multiple' => true,
'required' => false,
'attr' => array(
'class' => 'selectpicker',
'data-actions-box' => true,
'data-live-search' => true,
'data-selected-text-format' => 'count'
),
'horizontal' => false,
));
}
And then in the same formType add another method:
public function setDefaultOptions(OptionsResolverInterface $resolver) {
/*
* Instantiate DepartmentChoiceList and
* Implement the logic to build your Array and then set it this way
*/
$resolver->setDefaults(array(
'departmentChoiceList' => $yourDepartamentChoiceArray,
'data_class' => 'HotfloSystemCoreBundle:Department'
));
}
Note: I declared also the data_class here for the whole form, but you can also leave it out of the setDefaults if you want.
I suggest taking a look into the Entity type so you don't have to use a Transformer to convert object to int -> int to object for choice fields.

Modifying Symfony Forms html attributes / overriding a single attribute

I am trying to figure out how to modify html attributes on the fly with Symfony2 forms.
The situation is a case where a default placeholder is used most of the time, but occasionally, the developer needs to write a custom message.
My Form type looks like this:
<?php
namespace My\AwesomeBundle\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use My\AwesomeBundle\Transformer\ProcedureCodeTransformer;
class ProcedureType extends AbstractType
{
private $em;
private $user;
public function __construct($em, $securityContext)
{
$this->em=$em;
$this->user=$securityContext->getToken()->getUser();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new ProcedureTransformer( $this->em );
$builder->addModelTransformer( $transformer );
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$user = $this->user;
$resolver->setDefaults(
array(
'class' => 'My\AwesomeBundle\Entity\Procedure',
'label' => 'Procedure',
'label_attr' => array( 'class'=> 'control-label' ),
'required' => false,
'empty_value' => '',
'attr' => array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
),
)
);
$resolver->setOptional( array( 'placeholder' ) );
}
public function getParent() {
return 'hidden';
}
public function getName() {
return 'procedure';
}
}
The default render then has "Select Procedure" for the data-select2-placeholder element and javascript is used to display it. This is then used in a more complex type:
<?php
namespace My\AwesomeBundle\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProcedureType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('biller', 'billers', array( 'placeholder' => 'New Text'));
[...]
}
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults( array(
'data_class' => 'My\AwesomeBundle\Entity\Procedure'
) );
}
public function getName(){
return 'my_awesomebundle_proceduretype';
}
}
I would like 'New Text' to be placed into the data-select2-placeholder html attribute. However, if I call the builder like this:
$builder->add('procedure',
new ProcedureCodeType()),
array( 'attr' => array('data-select2-placeholder' => 'New Text') )
);
The entire html attribute array is replaced. this is not surprising. Is there a function in the form builder that has eluded me to add or modify a single html attribute?
Unfortunately, there is no way to modify a single attr the way you would want it. This is the closest and most simple solution I could come with:
What you have to do in your situation is to use callback functions in your options.
For each default attribute in your form types, instead of a normal value, you can use a callback function that accepts a single input representing your options (Symfony class Symfony\Component\OptionsResolver\Options). The function is called when building the form and the return value is then used as the value of the option.
That way, you can build the resulting option depending on the other provided options. An example:
use Symfony\Component\OptionsResolver\Options;
class ProcedureType extends AbstractType
{
...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$attr = function(Options $options) {
$default = array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
);
return array_replace($default, $options['new_attr']);
};
$user = $this->user;
$resolver->setDefaults(
array(
'class' => 'My\AwesomeBundle\Entity\Procedure',
'label' => 'Procedure',
'label_attr' => array( 'class'=> 'control-label' ),
'required' => false,
'empty_value' => '',
'attr' => $attr,
'new_attr' => array(),
)
);
$resolver->setOptional( array( 'placeholder' ) );
}
That way, what you have to modify will be new_attr.
You might want to create a new class that resolves the nested options for you.
<?php
namespace My\AwesomeBundle\Form\Attr;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\Options;
class ProcedureCodeAttr
{
/**
* #var array
*/
protected $options;
/**
* #param array $options
*/
public function __construct(array $options = array())
{
$resolver = new OptionsResolver();
$this->setDefaultOptions($resolver);
$this->options = $resolver->resolve($options);
}
/**
* #param OptionsResolverInterface $resolver
*/
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
));
}
/**
* #return array
*/
public function toArray()
{
return $this->options;
}
/**
* #param array $options
* #return array
*/
public static function resolve(array $options = array())
{
$attr = new static($options);
return $attr->toArray();
}
}
Then in your form type class you'd use it like so
$builder->add('procedure', new ProcedureCodeType()), array(
'attr' => ProcedureCodeAttr::resolve(array(
'data-select2-placeholder' => 'New Text'
))
));
Symfony 4.4 and above now support nested options https://symfony.com/doc/4.4/components/options_resolver.html#nested-options
Instead of defining a nested array, create a callback with OptionsResolver.
For example, a custom form type exists with the following defaults:
class CustomType extends AbstractType {
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('attr', function (OptionsResolver $attrResolver) {
$attrResolver->setDefaults([
'class' => 'form-control-lg',
'placeholder' => 'My Default Placeholder',
'autocomplete' => 'off',
]);
});
$resolver->setDefaults([
'label' => 'Name',
'help' => 'Enter a name',
]);
}
....
}
Attr is now a callback, not an array, now in any forms that use it just defined the attr properties like normal.
class MyFormType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', CustomType::class, [
'label' => 'My Form',
'help' => 'My Form Help',
'attr' => [
'placeholder' => 'My Form Placeholder',
],
])
....
}
The generated form will have the class: 'form-control-lg' and the label: 'My Form Placeholder'.

FOS UserBundle - Override the FormFactory

i need some help overriding the FormFactory.
My Goal is to change the Profile. So, as i also use Facebook Login, i do not want them to change email, username and password.
So i use the ProfileController in my bundle to hand over the current user to the ProfileFormType class.
What i'm trying to do is to implement my own FormFactory, so i can set the user and put it into the options array inside the call
return $this->formFactory->createNamed($this->name, $this->type, null, array('validation_groups' => $this->validationGroups, 'user' => $this->user));
To achieve this, i need to define my FormFactory in sevices.yml.
Here is the original one from FOSUserBundle:
<service id="fos_user.profile.form.factory" class="FOS\UserBundle\Form\Factory\FormFactory">
<argument type="service" id="form.factory" />
<argument>%fos_user.profile.form.name%</argument>
<argument>%fos_user.profile.form.type%</argument>
<argument>%fos_user.profile.form.validation_groups%</argument>
</service>
I have difficulties to translate this into yml, as i do not understand the usages of aliases completely.
Could you help me to define it correct? Something like
skt_user.profile.form.factory:
class: SKT\UserBundle\Form\Factory\FormFactory
arguments: ???
Funny, after posting it, I found the solution. This is the correct configuration for my FormFactory:
skt_user.profile.form.factory:
class: SKT\UserBundle\Form\Factory\FormFactory
arguments: ["#form.factory", "%fos_user.profile.form.name%", "%fos_user.profile.form.type%", "%fos_user.profile.form.validation_groups%"]
In my controller, I simply used these 2 lines:
$formFactory = $this->container->get('skt_user.profile.form.factory');
$formFactory->setUser($user);
In the factory, I implemented this function
namespace SKT\UserBundle\Form\Factory;
use Symfony\Component\Form\FormFactoryInterface;
use FOS\UserBundle\Form\Factory\FactoryInterface;
class FormFactory implements FactoryInterface
{
private $formFactory;
private $name;
private $type;
private $validationGroups;
private $user;
public function __construct(FormFactoryInterface $formFactory, $name, $type, array $validationGroups = null)
{
$this->formFactory = $formFactory;
$this->name = $name;
$this->type = $type;
$this->validationGroups = $validationGroups;
}
public function createForm()
{
return $this->formFactory->createNamed($this->name, $this->type, null, array('validation_groups' => $this->validationGroups, 'user' => $this->user));
}
public function setUser($user)
{
$this->user = $user;
}
}
and this is how my Formtype looks
<?php
namespace SKT\UserBundle\Form\Type;
use SKT\CaromBundle\Repository\PlayerRepository;
use Symfony\Component\Form\FormBuilderInterface;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProfileFormType extends \FOS\UserBundle\Form\Type\ProfileFormType
{
private $class;
/**
* #param string $class The User class name
*/
public function __construct($class)
{
$this->class = $class;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Do not show email and username if login uses facebook
if (!$options['user']->getFacebookId()) {
$builder
->add('email', 'email', array('label' => 'form.email', 'translation_domain' => 'FOSUserBundle'))
->add('username', null, array('label' => 'form.username', 'translation_domain' => 'FOSUserBundle'));
}
$builder
->add('firstname', null, array('label' => 'Vorname'))
->add('lastname', null, array('label' => 'Nachname'))
->add('player', 'entity', array(
'label' => 'Spieler',
'class' => 'SKTCaromBundle:Player',
'property' => 'name',
'query_builder' => function (PlayerRepository $er) {
return $er->createQueryBuilder('p')
->orderBy('p.name', 'ASC');
},
'empty_value' => 'Verbinde Dich mit einem Spieler',
'required' => false,
));
}
public function getName()
{
return 'skt_user_profile';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => $this->class,
'intention' => 'profile',
'user' => null
));
}
}
works perfect!

Resources