Symfony2.x to 3.x Forms - 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
]);

Related

Use CollectionType inside my ThreadType on Symfony display a field for each message, not only the first

<?php
class ThreadType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextareaType::class, [
'attr' => [
'class' => 'froala-form-title'
]
])
->add('messages', CollectionType::class, [
'label' => false,
'entry_type' => MessageType::class,
'entry_options' => [
'label' => false
],
]);
}
// ...
My form for create a new Thread is OK, I have my thread title field and the message field:
After that, I add multiple messages inside my thread, and when I want edit my Thread I have this problem:
My CollectionType field display one textarea by messages inside my thread, I only want edit the first message, how can I perform that on Symfony?
Solved with this code:
$firstThreadMessage = $options['data']->getMessages()[0];
$builder
->add('title', TextareaType::class, [
'attr' => [
'class' => 'froala-form-title'
]
])
->add('messages', MessageType::class, [
'mapped' => false,
'data' => $firstThreadMessage
])
;
For this formType $options['data'] is my Thread, I can easy get the first message of the thread with ->getMessages()[0];. mapped is false here but is handled by the MessageType 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'

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

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()
}

Additional Choice field for ordering

I want to set the order for new entry. I need order to first position and order after one of the existing entries
I don't know how to manage this in Symfony 4.2
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('nameDe')
->add('nameEn')
->add('descriptionDe')
->add('descriptionEn')
->add('rank', EntityType::class, [
'class' => ProductType::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('p')
->orderBy('p.rank', 'ASC');
},
'choice_label' => 'nameDe',
])
->add('active')
->add('creatDate')
->add('updateDate', DateTimeType::class, array('data' => new \DateTime()));
}
I have this:
<select id="product_type_rank" name="product_type[rank]">
<option value="1">Internetseiten</option>
<option value="2">Printmedia</option>
</select>
I need something like this
<select id="product_type_rank" name="product_type[rank]">
<option value="0">At the beginning</option>
<option value="1">after Internetseiten</option>
<option value="2">after Printmedia</option>
</select>
If you want to add custom text in your options, you can use the choice_label option like that :
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('nameDe')
->add('nameEn')
->add('descriptionDe')
->add('descriptionEn')
->add('rank', EntityType::class, [
'class' => ProductType::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('p')
->orderBy('p.rank', 'ASC');
},
'choice_label' => function($productType) {
return 'after ' . $productType->getNameDe();
},
'placeholder' => 'At the beginning',
])
->add('active')
->add('creatDate')
->add('updateDate', DateTimeType::class, array('data' => new \DateTime()));
}
With this one, you will have the expected result.
You can add a specific getter into your ProductType entity class and use it directly in the choice_label, like that :
ProductType.php
class ProductType {
...
getNameDeWithCustomText() {
return 'after ' . $this->getNameDe();
}
}
'choice_label' => 'nameDeWithCustomText',
If you want to add a default option to your EntityType field, you can add a placeholder option like this
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('nameDe')
->add('nameEn')
->add('descriptionDe')
->add('descriptionEn')
->add('rank', EntityType::class, [
'class' => ProductType::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('p')
->orderBy('p.rank', 'ASC');
},
'choice_label' => 'nameDe',
'placeholder' => 'At the beginning',
])
->add('active')
->add('creatDate')
->add('updateDate', DateTimeType::class, array('data' => new \DateTime()));
}
As I see, you are handling multiple languages in your form, if you want to internationalize your placeholder, just put the translation key directly in the value (you need to install the Symfony translation package : https://symfony.com/doc/current/translation.html)
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('nameDe')
->add('nameEn')
->add('descriptionDe')
->add('descriptionEn')
->add('rank', EntityType::class, [
'class' => ProductType::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('p')
->orderBy('p.rank', 'ASC');
},
'choice_label' => 'nameDe',
'placeholder' => 'foo.bar.placeholder',
])
->add('active')
->add('creatDate')
->add('updateDate', DateTimeType::class, array('data' => new \DateTime()));
}
If the translation files don't exist, create them in translations/ folder
# translations/messages.en.yaml
foo:
bar:
placeholder: At the beginning
# translations/messages.de.yaml
foo:
bar:
placeholder: Am Anfang

Add default choices manually

public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('agency', EntityType::class, [
'placeholder' => "select agency",
"choice_label" => "getName",
"class" => Agency::class,
"label" => "Associée à l'agence",
"required" => false,
]);
}
How can add default choices manually for exemple :
// agencies from DB
agancy1
agency2
agency3
test // add test manually