How to retrive data from non-mapped field in embeded form - forms

I would like to know if it's possible to get the data from a non-mapped field in an embeded form.
Here is the main form :
class PlayedLifeScoreType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//->add('nom')
//->add('prenom')
// NOTE: Use form collection to allow multiple `played` forms per JoueurType
->add('playeds', CollectionType::class, [
'entry_type' => PlayedLifeType::class,
'label' => false,
])
->add('submit', SubmitType::class, [
'attr' => ['class' => 'save'],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Partie::class,
]);
}
}
And the embedded one :
class PlayedLifeType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
/*->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$form->add('joueur', null, array(
'data' => $event->getData() ?: options['joueur']
))*/
//->add('joueur')
->add('joueur', TextType::class, [
'label' => false,
'disabled' => 'true',
'attr' => ['class' => 'tinymce'],
])
->add('max')
->add('score')
->add('round', IntegerType::class,[
'mapped' => false,
'label' => 'Round',
])
;
//});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Played::class,
'joueur' => null
]);
}
}
And want to get the data from "round". I tried like this but doesn't work :
$r = $mainForm->get('playeds')->get("round")->getData();
I get this error :
Child "round" does not exist.
Any idea ?

field "playeds" is a CollectionType.
So, for each entry, there is a 'round' value
To access this, you should do something like:
foreach ($mainForm->get('playeds') as $played) {
//you can access round here with $played->get('round')->getData()
//Or the Played object with $played->getData()
}

Related

Embed nested collections form in Symfony 6

I want to embed nested collections on three levels, for the second level it works but not the last one i can't get the prototype on the last level.
I work with Symfony 6 and i use stimulus.
class FormType1 extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field1')
->add('formType2', CollectionType::class, [
'entry_type' => FormType2::class,
'entry_options' => ['label' => false],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => FormData1::class,
]);
}
}
class FormType2 extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field2')
->add('formType3', CollectionType::class, [
'entry_type' => FormType3::class,
'entry_options' => ['label' => false],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => FormData2::class,
]);
}
}
class FormType3 extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field3')
->add('field4');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => FormData3::class,
]);
}
}
Here is the prototype:
<div {{ stimulus_controller('form-collection') }}
data-form-collection-index-value="{{ form.formType3|length > 0 ? form.formType3|last.vars.name + 1 : 0 }}"
data-form-collection-prototype-value="{{ form_widget(form.formType3.vars.prototype)|e('html_attr') }}" <<============
>
<ul {{ stimulus_target('form-collection', 'collectionContainer') }}></ul>
<button type="button" {{ stimulus_action('form-collection', 'addCollectionElement') }}>Add a tag</button>
</div>
In Stimulus Controller form-collection i get the prototype in this way
item.innerHTML = this.prototypeValue.replace(/__name__/g, this.indexValue);
The prototypeValue It return an empty string

How-To: A form with a collection of forms which contains another collection

I have to create a simple system for some polls starting with 3 entities: VotingSession, Question and Answer.
Between VotingSession and Question is a One-To-Many relationship.
Between Question and Answer is a One-To-Many relationship.
What I'm trying to achieve is the next scenario: Answers can have multiple types. For examples, you can have radiobox inputs where you will choose one answer, you can have checkbox inputs where you will choose 1 or more items or text input fields where you will fill whatever you want.
I already pass a VotingSession object to the first form and entire form is displayed perfectly, so there's no problem here. What I discovered is that when I access $builder->getData() inside the buildForm() method on QuestionForm I get value NULL, even form is filled.
My idea is to try to achieve my goal based on passed value, but I can't do that.
Do you have any idea what I'm doing wrong or any advice?
I have the following forms:
VotingSessionForm:
/**
* #inheritDoc
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('questions', CollectionType::class, [
'entry_type' => QuestionForm::class,
'by_reference' => true,
'prototype' => true,
'prototype_name' => '__question__',
'entry_options' => [
'label' => false,
],
'constraints' => [
new Valid(),
],
]);
}
/**
* #inheritDoc
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'error_bubbling' => false,
'data_class' => VotingSession::class,
]);
}
QuestionForm:
/**
* #inheritDoc
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('answers', CollectionType::class, [
'entry_type' => AnswerForm::class,
'prototype' => true,
'prototype_name' => '__answers__',
'entry_options' => [
'label' => false,
],
'constraints' => [
new Valid(),
],
]);
}
/**
* #inheritDoc
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Question::class,
]);
}
AnswerForm:
/**
* #inheritDoc
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label', TextType::class, [
'label' => false,
'disabled' => true,
])
->add('value', NumberType::class, [
'label' => false,
'mapped' => false,
]);
}
/**
* #inheritDoc
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Answer::class,
]);
}

How to access to an entity instance in a subform?

Summary
I want to access to an entity instance (of Media) inside a form related to an entity "Media" which is related to a form related to an entity "Project". How can I access to this entity instance ?
What i have tried
I already found on the net that some people talked about an event callback like this :
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
/** #var YourEntity $data */
$data = $event->getData();
});
But that is only interesting when we had clicked on a 'add' button.
I want my entity on the first display of the page to show my image so that the user can see it and edit it if he wants to.
After, the user can add other images but it is another thing.
I also found some persons that say to put 'allow_add' to false, but it is not solving my problem.
The code
Here is my two forms.
The form Project :
class Project extends AbstractForm
{
use ArticleForm;
use StatusForm;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('medias', CollectionType::class, [
'entry_type' => Media::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true
])
->add(
'teaser',
TextareaType::class,
[
'label' => 'label.teaser',
]
)
->add(
'public',
CheckboxType::class,
[
'label' => 'label.public',
]
);
$this->builderAddTitleAndBody($builder);
$this->builderAddStatus($builder, $options['data']);
$this->builderAddSave($builder);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => \App\Entity\Project::class,
]);
}
}
And the form Media :
class Media extends AbstractForm
{
use EntityForm;
public $formName = 'media';
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'file',
FileType::class,
[
'data' => $this->entityFileInit($builder->getData(), 'file'),
'label' => 'label.picture',
'required' => false,
'mapped' => false,
'data_class' => null
]
)
->add(
'alt',
TextType::class,
[
'label' => 'label.alt',
'mapped' => false
]
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => \App\Entity\Media::class,
]
);
}
}
Expected and actual results
I want Symfony to come at least to the template but Symfony tells me that $builder->getData() returns 'null'

Options to subform

How i can give form's options to a subform ?
In the example below, i have the option "special" declared.
I want to access this option "special" in my subform.
My main form :
class DemandeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', TextType::class, []);
$builder->add('service_agent', ServiceType::class, [
'mapped' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'special' => true
]);
}
And my subform :
class ServiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
dump($options['special']);
$builder->add('service', TextType::class, []);
$builder->add('agent', TextType::class, [
'mapped' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
}
I answer my question.
I must declare option "special" in subform.
And this field's option is accessible in the main form.
Like that :
class DemandeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', TextType::class, []);
$builder->add('service_agent', ServiceType::class, [
'mapped' => false,
'special' => $options['special']
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'special' => true
]);
}
And in my subform :
class ServiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
dump($options['special']); // It's OK :)
$builder->add('service', TextType::class, []);
$builder->add('agent', TextType::class, [
'mapped' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'special' => true
]);
}

Symfony2.x to 3.x Forms

In Symfony 2.x, I used this way to display form with custom fields
ProductType form class
public function __construct(array $aCustomFieldsList)
{
$this->aCustomFields = $aCustomFieldsList;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, [ "label" => "Name" ])
->add('price', MoneyType::class, [ "label" => "Price" ]);
foreach( $this->aCustomFields as $n=>$sCustomField )
{
$builder->add($sCustomField, TextType::class, [ "label" => {$sCustomField}"]);
}
$builder->add('save', SubmitType::class, [ "label" => "Save" ]);
}
And in the controller
$form = $this->createForm(new ProductType($aCustomFields), $product);
But in Symfony 3.x, the first argument of the method createForm() don't expect instanciated object anymore, but a string representing the class name with namespace, like this :
$form = $this->createForm(ProductType::class, $product);
What is the way to do the same as I did with Symfony 3.x ?
As Cerad suggests you can pass those options in using the options field. This was recommended even back in 2.x. You then define the option requirements for it. If you make the field required like i have then you will get errors if its not provided when you build the form.
// Form Type
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, [ "label" => "Name" ])
->add('price', MoneyType::class, [ "label" => "Price" ]);
foreach( $options['custom_field_list'] as $n=>$sCustomField )
{
$builder->add($sCustomField, TextType::class, [ "label" => {$sCustomField}]);
}
$builder->add('save', SubmitType::class, [ "label" => "Save" ]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'custom_field_list' => [],
]);
$resolver->setRequired([
'custom_field_list' => true
]);
$resolver->setAllowedTypes([
'custom_field_list'=>'array'
]);
}
Then call it in the controller:
//controller
$form = $this->createForm(ProductType::class, $product, [
'custom_field_list' => $aCustomFields
]);