Symfony inject parameter to form from buildForm - php

I'm using Symfony <4 and I have an issue with a form using an other form in a Many-To-Many relation with parameter.
You will find below my FeatureForm:
->add('tags',CollectionType::class,
array(
'entry_type' =>TagFeatureType::class,
'allow_add' => true,
'allow_delete' => true,
'data' => $datas,
'entry_options' => array(
'label' => false,
)
)
)
Now my TagFeatureType:
->add('tag', EntityType::class,
array(
'choice_label' => 'name',
'class' => 'AppBundle:Tag',
'query_builder' => function(TagRepository $tr){
return $tr->findObjectNotMine();
}
)
)
I would like to inject a parameter into findOBjectNotMine but I cannot pass parameter from controller because the TagFeatureType is created by FeatureForm. Inside the buildForm function I cannot pass any extra parameter.
I see 2 possibilites, 1st I modify default options to allow an extra option, but it's a bit disgusting. 2nd, I could use session parameter and inject session service inside the constructor... But it looks like more a workaround than a proper way...
Do you know an elegant way to inject parameter to a form from buildForm function inside FormType?
Best regards

In case you need to pass container-known parameters to custom form type, you can go the way you attempted above (obviously via parameter injection). However, if you want to pass data from controller down to form type, you can pass it via $options (last) argument (in buildForm):
FeatureForm
public function buildForm(FormBuilderInterface $builder, array $options)
// ....
$builder->add('tags', CollectionType::class, array (
'entry_type' => TagFeatureType::class,
'allow_add' => true,
'allow_delete' => true,
'data' => $datas,
'entry_options' => array(
'label' => false,
'some_custom_param' => $options['some_custom_param']
)
)
);
// ....
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Task::class
));
$resolver->setRequired(array(
'some_custom_param'
));
}
And then, in TagFeatureType should have configured options:
TagFeatureType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired(array(
'some_custom_param'
));
}
And then finally, include it inside of buildForm:
public function buildForm(FormBuilderInterface $builder, array $options)
// ....
$someCustomParam = $options['some_custom_param'];
// ....
$builder->add('tag', EntityType::class, array(
'choice_label' => 'name',
'class' => 'AppBundle:Tag',
'query_builder' => function(TagRepository $tr) use ($someCustomParam)
{
return $tr->findObjectNotMine($someCustomParam);
}
);
// ....
}
Obvious downside of this would be a need for all the forms in path to have setRequired or setDefault.
Hope this helps...

In your TagFeatureType you can just pass in the argument via constructor injection:
class TagFeatureType extends AbstractType
{
private $tagRepository;
public function __construct(TagRepository $tagRepository)
{
$this->tagRepository = $tagRepository;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$tr = $this->tagRepository;
// ...
->add('tag', EntityType::class,
array(
'choice_label' => 'name',
'class' => 'AppBundle:Tag',
'query_builder' => function(TagRepository $tr){
return $tr->findObjectNotMine();
}
)
)
}
}
There is a DependencyInjectionExtension form extension that will check if the form type is registered in the service container and then inject it, instead of creating a new instance. This should make sure that the tag repository exists.

Related

Use `$options` in an `EntityType` Query_builder

I use a Form Builder to create my form. I added the option team to the FormBuilderInterface to access this property in my form.
class PersonnalStatType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('tag', EntityType::class, [
'class' => StatTag::class,
'choice_label' => 'name',
'query_builder' => function (StatTagRepository $rep/*, $options*/)
{
return $rep->queryActivated($options['team']);
}
]
)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => PersonnalStat::class,
'team' => null
]);
$resolver->setAllowedTypes('team', ['null', Team::class]);
}
The first row is an EntityType and I need to use the team option to perform a query but I don't know how to proceed.
So far I tried :
'query_builder' => function (StatTagRepository $rep)
{
return $rep->queryActivated($options['team']);
}
but the query doesn't know $options
Notice: Undefined variable: options
And
'query_builder' => function (StatTagRepository $rep, $options)
{
return $rep->queryActivated($options['team']);
}
but it doesn't recognize $options
Too few arguments to function App\Form\PersonnalStatType::App\Form{closure}(), 1 passed in /Users/pierrickrambaud/Sites/team-manager/vendor/symfony/doctrine-bridge/Form/Type/EntityType.php on line 32 and exactly 2 expected
Anyone knows a hack ?
This should work:
'query_builder' => function (StatTagRepository $rep) use ($options) {
return $rep->queryActivated($options['team']);
}

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

Symfony forms - mapping of checkbox

Can I specify how the form field shold be mapped on data class?
Let's say I have form with check box and on my data entity the field is stored as string.
class FormType extends AbstractType {
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => DataEntity::class,
]);
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('issueType', CheckboxType::class, [
'label' => 'issueType',
]);
}
}
class DataEntity {
/** #var string Either PLASTIC or PAPER */
private $issueType;
public function getIssueType() {
return $this->issueType;
}
public function setIssueType($issueType) {
$this->issueType = $issueType;
}
}
Can I made the checkbox to be mapped as 'PLASTIC' if ture and 'PAPER' if false?
You can use data transformer to cast bolean to the string. See this tutorial: https://symfony.com/doc/current/form/data_transformers.html.
$builder->get('issueType')
->addModelTransformer(new CallbackTransformer(
function ($type) {
// your logic here
return $type;
},
function ($type) {
// your logic here
return $type;
}
));
try :
$builder->add('newsletter', 'choice', array(
'label' => 'Newsletter erhalten',
'attr' => array(
'class' => 'form-control',
),
'choices' => array(array('yes' => 'plastic'), array('no' => 'paper')),
'expanded' => true,
'multiple' => true,
'required' => false,
));
also check the answer here Symfony2 Change checkbox values from 0/1 to 'no'/'yes'

Get value from EntityType Symfony

I can not get value from EntityType. I have last version 3.3.6.
class BuildType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array
$options)
{
$builder
->add('title', TextType::class)
->add('save', SubmitType::class, array('label' => 'Create Post'))
->add('team', CollectionType::class, array(
// each entry in the array will be an "email" field
'entry_type' => TeamType::class,
// these options are passed to each "email" type
'entry_options' => array(
'attr' => array('class' => 'form-control'),
),
'label' => false,
'allow_add' => true,
'prototype' => true,
'mapped' => false
));
}
}
class TeamType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array
$options)
{
$builder
->add('name', EntityType::class, array(
'placeholder' => 'Choice a champ',
'required' => true,
'class' => 'AppBundle:Champions',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.name', 'ASC');
},
'choice_label' => 'name',
'choice_value' => 'id',
'attr' => array('class' => 'dropdown'),
));
}
I tried all but i cannot take value of 'name' of TeamType. After submission form, i do
foreach ($form["team"]->getData() as $value) {
'name' => $value['name']
but the value is empty. If i try dump request and the value is there. The other values i can get it and save in Database. Only EntityType i can not.
Someone know how do?
EntityType returns as the object. You should use model getter functions.
$form->get('name')->getData()->getId(); // getName() vs..
Here is a similar example.
symfony how get selected data from select
I suppose that you are using ManyToOne relationship.
With AJAX
if you are trying get data after submitted, You can do this first in your view:
$('#elementWhereYouAreTEAMType').find('input').each(function (i,v) {
var valTeam = $(this).val(); //take value
// adding data, create an associative array.
formData.append("team["+ i +"]", valTeam);
});
You must put formData like data argument to ajax JQuery
Now, server side:
public function createTeamNow(Request $request) {// ajax
$teams = $request->request->get('team'); // getting array
if(!is_null($teams)) {// if user added team
foreach ($teams as $team) {
// dump($team);
//create an instance for each element, it does not replace a data with the above
$teamType = new TeamType();
$teamName->setName($team);
$this->em->persist($teamType);
}
}
}
Without AJAX
/**
* #Route("/slim/1" , name="data_x")
*/
public function slimExampleAction(Request $request)
{
$form = $this->createForm(TeamType::class);
$form->handleRequest($request);
if ($form->isSubmitted() /*&& $form->isValid()*/) {
// When you've ManyToOne relationship, that field returns ArrayCollection class, it has several method to get data on differents ways, iterate with it, for example toArray is one
$data = $form->getData();
dump($data->getDescription()->toArray());die;
}
return $this->render('AppBundle:view.html.twig', array(
'form' => $form->createView(),
));
}

Symfony 2 - how to pass data to formBuilder?

I'm using entity choice list in my form. I want to use only specific entities (in example: only groups that user belongs to)
So, in controller, I'm getting these groups, and trying to pass them into formBuider.
Controller:
/.../
$groups = $em->getRepository('VendorMyBundle:Group')->getUserGroups($user);
$form = $this->createForm(new Message($groups), $message);
/.../
so, what now? how to use it in formBuilder?
how to change this line to use passed array of groups?
->add('group','entity',array('class' => 'Vendor\MyBundle\Entity\Group', 'label'=>'Group:'))
or in the other way:
class MessageType
{
/.../
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('group','entity',
array(
'class' => 'Vendor\MyBundle\Entity\Group',
'property' => 'name',
'query_builder' => function ($repository) {
$qb = $repository->createQueryBuilder('group');
$qb->add('where', 'group.administrator = :user');
$qb->setParameter('user', $user->getId());
return $qb;
},
'label' => 'Group'
)
)
// Continue adding fields
;
}
/.../
}
so how can i get object $user to use in form builder? ($user represent current logged user)
You can give the object you want to use in the __construct() method.
Eg :
$form = $this
->get('form.factory')
->create(new ApplyStepOneFormType($this->company, $this->ad), $applicant);
In your form type :
function __construct(\Your\Bundle\Entity\Company $company, \DYB\ConnectBundle\Entity\Ad $ad) {
$this->company = $company;
$this->ad = $ad;
}
And then in your form type in buildForm method :
$company = $this->company;
$builder->add('ad', 'entity', array(
'class' => '\Your\Bundle\Entity\Ad',
'query_builder' => function(\Your\Bundle\Repository\AdRepository $er) use ($company) {
return $er->getActiveAdsQueryBuilder($company);
},
));
//In controller pass the value which you want to use in builder form in array like
$object = new Question();
$form->create(new QuestionType() , $object , array('sqtname'=>2,'question_type'=>2));
//In Form type class
public function buildForm(FormBuilderInterface $builder , array $options)
{
//for setting data field dynamically
if (array_key_exists('question_type', $options) && $options['question_type'] != '') {
$data = $em->getReference("RecrutOnlineStandardBundle:StdQuestionType",$options['question_type']->getId());
} else {
$data = "";
}
$builder->add('StdQuestionType', 'entity', array(
'class' => 'TestStandardBundle:StdQuestionType',
'property' => 'name',
'empty_value' => 'Sélectionner un question type',
'required' => true,
'data' => $data,
'query_builder' => function(EntityRepository $er ) use ( $options ) {
if (isset($options['sqtname']) && $options['sqtname'] != '') {
return $er->createQueryBuilder('sqt')
->where("sqt.name!= ".$options['sqtname']);
} else{
return $er->createQueryBuilder('sqt');
}
}
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Test\QuestionBundle\Entity\Question',
'required' => false,
'sqtname' => '',
'question_type' =>''
));
}
Bacteries' solution IS NOT a good one. For example, if you declare your type as service, it is impossible to pass an object to constructor.
A perfect solution is options - just pass data as option to form builder.
If you want to use custom query, you have to set query_builder option as follows:
use Doctrine\ORM\EntityRepository;
...
$message = new Message();
$form = $this->createFormBuilder($message)
->add('group', 'entity', array(
'class' => 'Vendor\MyBundle\Entity\Group',
'label'=>'Group:',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('g')
->... // whatever you want to do
}
))
->getForm();
You can find more info about query builder in Doctrine manual and about options for entity in Symfony2 manual.
Bacteries' solution is a real good one. Just a note to save headache to other guy like me :)
In this part may I point out the use ($company) part.
It was hidden by the frame and of course nothing works properly without it.
$builder->add('ad', 'entity', array(
'class' =>
'\Your\Bundle\Entity\Ad',
'query_builder' =>
function(\Your\Bundle\Repository\AdRepository $er) use ($company) {
return $er->getActiveAdsQueryBuilder($company);
},
)
);
Best way (my opinion) is give to your form entityManager and select all you need in it. But don't forget to declare empty key in setDefaults() otherwise data won't pass to your builder.
Something like this one
public function buildForm(FormBuilderInterface $builder, array $options)
{
$options['em']->getRepository(''); // select all you need
$builder->add('title', 'text')
->add('content', 'textarea');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Main\BlogBundle\Entity\Post',
'validation_groups' => array('post'),
'required' => false,
'em' => null // this var is for your entityManager
));
}
Apply EM as simple option...

Categories