How to access to an entity instance in a subform? - forms

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'

Related

Can't get a way to read the property "title" in class "App\Entity\Travel" triggered by $form->createView()

I am getting the following error on my symfony page. Using a CRUD system when I try to edit one item, I fall onto the following error:
Can't get a way to read the property "title" in class
"App\Entity\Travel".
My "Travel" entity has no such "title" property as it is not expected. The only place a "title" property is defined is in the TravelTranslation entity, which is in a ManyToOne relationship with travel.
After I have commented all references to the form in my twig templates all I find is that the error is triggered by $form->createView() in my controller.
/**
* #Route("/{id}/edit", name="travel_edit", methods={"GET","POST"})
*/
public function edit(Request $request, Travel $travel): Response
{
$entityManager = $this->getDoctrine()->getManager();
$langs = $entityManager->getRepository(Lang::class)->findAll();
$media = $entityManager->getRepository(Media::class)->findAll();
$form = $this->createForm(TravelType::class, $travel);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->flush();
return $this->redirectToRoute('travel_index');
}
return $this->render('crud/travel/edit.html.twig', [
'langs' => $langs,
'travel' => $travel,
'media' => $media,
'form' => $form->createView()
]);
}
But then my TravelType form contains the following code:
class TravelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('main_title')
->add('category', EntityType::class,[
'class' => Category::class,
'choice_label' => 'name',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('c')
->andWhere('c.type = :type')
->setParameter('type', 'country')
->orderBy('c.name', 'ASC');
},
])
->add('price_driver', MoneyType::class,[
'divisor' => 100,
])
->add('price_codriver', MoneyType::class,[
'divisor' => 100,
])
/* ->add('country') */
->add('km')
->add('media', EntityType::class, [
'class' => Media::class,
'choice_label' => 'name',
'multiple' => true
])
->add('status')
->add('duration')
->add('level')
->add('travelTranslations', CollectionType::class, [
'entry_type' => TravelTranslationType::class,
'entry_options' => [
'label' => false
],
'by_reference' => false,
// this allows the creation of new forms and the prototype too
'allow_add' => true,
// self explanatory, this one allows the form to be removed
'allow_delete' => true
])
->add('dates', CollectionType::class, [
'entry_type' => DatesType::class,
'entry_options' => [
'label' => false
],
'by_reference' => false,
// this allows the creation of new forms and the prototype too
'allow_add' => true,
// self explanatory, this one allows the form to be removed
'allow_delete' => true
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Travel::class,
'allow_extra_fields' => true
]);
}
}
I managed to fix the error by getting into the DatesType::class (a custom entity type form not DateType) and fixing it as it was the one getting with the trouble of referring to the wrong property "title" in a collection type
By changing
->add('travel', EntityType::class, [
'class' => Travel::class,
'choice_label' => 'title'
] )
By:
->add('travel', EntityType::class, [
'class' => Travel::class,
'choice_label' => 'main_title'
] )
Where main_title is the real property.

Symfony : Entity of type "App\Entity\Classement" passed to the choice field must be managed. Maybe you forget to persist it in the entity manager?

I have this form :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('country', EntityType::class, [
'class' => Country::class,
'choice_label' => 'name',
'label' => false,
'placeholder' => '-',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.name', 'ASC');
}
])
->add('season', EntityType::class, [
'class' => Season::class,
'choice_label' => 'year',
'label' => false,
'placeholder' => '-',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('s')
->orderBy('s.year', 'ASC');
}
])
->add('clubHistos', CollectionType::class, [
'entry_type' => ClubHistoType::class,
'entry_options' => [
'label' => false
],
'by_reference' => false,
'allow_add' => true
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Classement::class,
]);
}
Here is my ClubHistoType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('position', null, [
'label' => false
])
->add('matches', null, [
'label' => false
])
->add('victories', null, [
'label' => false
])
->add('draws', null, [
'label' => false
])
->add('losses', null, [
'label' => false
])
->add('goals', null, [
'label' => false
])
->add('points', null, [
'label' => false
])
->add('season', EntityType::class, [
'class' => Season::class,
'choice_label' => 'year',
'label' => false,
'placeholder' => '-',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('s')
->orderBy('s.year', 'ASC');
}
])
->add('club', EntityType::class, [
'class' => Club::class,
'choice_label' => 'name',
'label' => false,
'required' => false,
'placeholder' => '-',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.name', 'ASC');
}
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ClubHisto::class,
]);
}
And my function to create a new country table in my controller :
/**
* #Route("back/table/new", name="new_table")
*/
public function createCountryTable(Request $request, EntityManagerInterface $manager) {
$countryTable = new Classement();
$clubHistoriques = new ClubHisto();
$countryTable->addClubHisto($clubHistoriques);
$form = $this->createForm(ClassementType::class, $countryTable);
dd($form);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
// foreach($clubHistoriques as $histo) {
// $histo->setSeason($countryTable->getSeason());
// }
$manager->persist($countryTable);
$manager->flush();
return $this->redirectToRoute('edit_table', ['id' => $countryTable->getId()]);
}
return $this->render('back/createTables.html.twig', [
'form' => $form->createView()
]);
}
But as soon as I call the form, it gives me this error :
Entity of type "App\Entity\Classement" passed to the choice field must be managed. Maybe you forget to persist it in the entity manager?
I don't really understand because I have other forms that work exactly like this one and everything works fine...
I know this subject has been asked many times, but the solutions never worked that were given never really worked.
Your help is highly appreciated !
Try just instantiate your form without $countryTable
$form = $this->createForm(ClassementType::class);
You already bind the form to the data so you shouldn't need to pre-instantiate it.
Then handle the data inside your if (form->isSubmitted()) logic
/* #var $countryTable \App\Entity\Classement */
$countryTable = $form->getData();
$clubHistos = $countryTable->getClubHistos();

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,
]);
}

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