Symfony 5 : handling multiple forms in one controller - symfony

I am trying to manage multiple forms in the same page in Symfony 5 with the following function, but it seems that every time I try to submit a form, only the first form of the list is handled even if it is not the one that has been submitted:
class ContentController extends AbstractController
{
/**
* #Route("/category/edition/{content}", name="edit_category")
*/
public function edition(Request $request, Category $content): Response
{
$forms = [
"edit_category" => $this->createForm(EditCategoryType::class, $content),
"create_post" => $this->createForm(CreatePostType::class)
];
foreach($forms as $form) {
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
// Always prints edit_category
// even when that is the the create_post that is submitted
return var_dump($form->getName());
}
}
return $this->render(
'content/edition.html.twig',
[
'forms' => \array_map(
function($form) {
return $form->createView();
},
$forms
),
'content' => $content,
]
);
}
}
I have seen in other posts that the name of the forms can sometime raise an issue, but I have checked that the forms do have different names, and I have also tried to call handleRequest() on every form in a separate foreach loop because I have seen this done in some posts, but it (quite expectedly I must say) didn't change the behavior.
And I didn't seem to find any unanimous best practice tips about how to handle multiple forms in the same Controller in Symfony, so I was wondering what is the best way to do it, or if it would be cleaner to define a separate action route for each form in order to avoid this problem altogether.
If needed, the content/edition.html.twig file looks something like that:
{% set edit_category_form = forms['edit_category'] %}
{% set create_post_form = forms['create_post'] %}
{{ form_start(edit_category_form) }}
{{ form_errors(edit_category_form) }}
{{ form_end(edit_category_form) }}
{{ form_start(create_post_form) }}
{{ form_errors(create_post_form) }}
{{ form_end(create_post_form) }}
(Category is a classical Symfony entity, EditCategoryType is a form associated with the Category entity and CreatePostType is a form associated with another Symfony entity)

After some research, it seems for some reason like it works if (and only if?) the form is build just before handling the request:
class ContentController extends AbstractController
{
/**
* #Route("/category/edition/{content}", name="edit_category")
*/
public function edition(Request $request, Category $content): Response
{
$self = $this;
$formBuilders = [
"edit_category" => function() use ($self, $content) {
return $self->createForm(EditCategoryType::class, $content);
},
"create_post" => function() use ($self, $content) {
return $self->createForm(CreatePostType::class);
},
];
$forms = [];
foreach($formBuilders as $key => $formBuilder) {
$form = $formBuilder();
$forms[$key] = $form;
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
// Does print the name of the right form
return var_dump($form->getName());
}
}
return $this->render(
'content/edition.html.twig',
[
'forms' => \array_map(
function($form) {
return $form->createView();
},
$forms
),
'content' => $content,
]
);
}
}
It works but it doesn't feel like a proper way to handle this !

Related

How do I retrieve datas sent by search form when I go back to my events list?

I'd like to know how I can go back to route events from event with the results obtained with the form at search. At the moment, when I try to go back from event to events, I lost all the datas that allowed me to find events according to their bigcity and their category.
Is there a way to retrieve these informations ?
event.html.twig
{% extends 'base.html.twig' %}
{% block main %}
<a href="{{ path('events') }}" >< Retour</a>
EventController.php
<?php
namespace App\Controller\Front;
use App\Entity\Event;
use App\Form\SearchType;
use App\Repository\UserRepository;
use App\Repository\EventRepository;
use App\Repository\BigCityRepository;
use App\Repository\CategoryRepository;
use App\Repository\LocationRepository;
use Doctrine\ORM\EntityManagerInterface;
use App\Repository\ParticipationRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class EventController extends AbstractController
{
#[Route('/search', name: 'search')]
public function search(
Request $request,
SessionInterface $sessionInterface,
EventRepository $eventRepository,
){
$searchFormData = $sessionInterface->get('searchFormData');
$form = $this->createForm(SearchType::class, ['data' => $searchFormData]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$sessionInterface->set('searchFormData', $data);
$events = $eventRepository->findAll();
return $this->redirectToRoute("events", [
'bigcity'=> $form->get('bigcity')->getData()->getId(),
'category'=> $form->get('category')->getData()->getId(),
'events' => $events
]);
}
return $this->renderForm('front/search.html.twig', [
'form' => $form,
]);
}
#[Route('/events', name: 'events')]
public function events(
Request $request,
EventRepository $eventRepository,
CategoryRepository $categoryRepository,
BigCityRepository $bigcityRepository,
LocationRepository $locationRepository
){
$category = $categoryRepository->findOneById($request->query->get('category'));
$bigcity = $bigcityRepository->findOneById($request->query->get('bigcity'));
$location = $locationRepository->findAll($request->query->get('location.bigcity'));
$events = $eventRepository->comingEventsList(['category' => $category, 'address' => $location]);
return $this->render("front/events.html.twig", [
'events' => $events,
'category' => $category,
'bigcity' => $bigcity
]);
}
#[Route('/event/{id}', name: 'event', methods: ['GET'])]
public function event(
Event $event,
UserRepository $userRepository,
ParticipationRepository $participationRepository
){
if ($this->getUser()) {
$connected = $this->getUser();
$useremail = $connected->getUserIdentifier();
$user = $userRepository->findOneBy(['email' => $useremail]);
if ($participationRepository->findby(['participant' => $user, 'event' => $event])) {
$participation = $participationRepository->findby(['participant' => $user, 'event' => $event]);
return $this->render('front/event.html.twig', [
'event' => $event,
'participation' => $participation
]);
}}
return $this->render('front/event.html.twig', [
'event' => $event
]);
}
Each event is related to one category and one bigcity and the form search form has just this tow fields
So, when you are in the show one event route, you can know what is the category and what is the bigcity from the current event.
You can add those two attributes id in the path like this:
Retour
Or you can use "referer" also, but this will not work everytime because if the user comes to show one event from history of borwser or from an extern url or if he types the url directly in the browser url tab
Rtour
Or with javascript

Populate field dynamically based on the user choice value in the another field in Symfony 5

I am trying to "Update field dynamicly based on the user choice in another field in Symfony 5" from "https://symfony.com/doc/current/form/dynamic_form_modification.html#form-events-submitted-data" this side. But i faced this exceptions/error "Argument 2 passed to App\Form\SportMeetupType::App\Form{closure}() must be an instance of App\Form\Sport or null, instance of App\Entity\Sport given, called in C:\Apache24\htdocs\dynamicform\src\Form\SportMeetupType.php on line 58" when ajax called.
My Code:
Form :
class SportMeetupType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('sport', EntityType::class, [
'class' => 'App\Entity\Sport',
'placeholder' => '',
])
;
$formModifier = function (FormInterface $form, Sport $sport = null) {
$positions = null === $sport ? [] : $sport->getPositions();
$form->add('position', EntityType::class, [
'class' => 'App\Entity\Position',
'placeholder' => '',
'choices' => $positions,
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
$formModifier($event->getForm(), $data->getSport());
}
);
$builder->get('sport')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$sport = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), $sport); // Here line 58 and error show this line
}
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => SportMeetup::class,
]);
}
}
Controller:
/**
* #Route("/new", name="sport_meetup_new", methods={"GET","POST"})
*/
public function new(Request $request): Response
{
$sportMeetup = new SportMeetup();
$form = $this->createForm(SportMeetupType::class, $sportMeetup);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($sportMeetup);
$entityManager->flush();
return $this->redirectToRoute('sport_meetup_index');
}
return $this->render('sport_meetup/new.html.twig', [
'sport_meetup' => $sportMeetup,
'form' => $form->createView(),
]);
}
Twig/View:
{% extends 'base.html.twig' %}
{% block title %}New SportMeetup{% endblock %}
{% block body %}
<h1>Create new SportMeetup</h1>
{{ form_start(form) }}
{{ form_row(form.name)}}
{{ form_row(form.sport) }} {# <select id="sport_meetup_sport" ... #}
{{ form_row(form.position) }} {# <select id="sport_meetup_position" ... #}
{# ... #}
{{ form_end(form) }}
{% endblock %}
JavaScript code:
var $sport = $('#sport_meetup_sport');
// When sport gets selected ...
$sport.change(function() {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected sport value.
var data = {};
data[$sport.attr('name')] = $sport.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url : $form.attr('action'),
type: "POST",
data : data,
success: function(html) {
// Replace current position field ...
$('#sport_meetup_position').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#sport_meetup_position')
);
// Position field now displays the appropriate positions.
}
});
});
When changed Sport dropdown value then ajax called and show below exceptions/error
"Argument 2 passed to App\Form\SportMeetupType::App\Form{closure}() must be an instance of App\Form\Sport or null, instance of App\Entity\Sport given, called in C:\Apache24\htdocs\dynamicform\src\Form\SportMeetupType.php on line 58"
Please help me....
Make sure that in your SportMeetupType.php you have:
use App\Entity\Sport;
I think you are using wrong class.

Create a custom route in a custom page

I'm using Symfony 4.3 and Sonata 3.x version.
I'm trying to create a custom route in a custom Page but I get the error :
An exception has been thrown during the rendering of a template ("unable to find the route `admin.photocall|admin.photocall_gallery.moderate`")
I have an entity X with a OneToMany relation to the Y entity.
Explanation with code :
class XAdmin extends AbstractAdmin
{
[...]
protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
{
$admin = $this->isChild() ? $this->getParent() : $this;
$id = $admin->getRequest()->get('id');
if ($this->isGranted('LIST')) {
$menu->addChild('Galerie', [
'uri' => $admin->generateUrl('admin.photocall_gallery.list', ['id' => $id])
]);
}
}
}
Then there is my YAdmin :
class YAdmin extends AbstractAdmin
{
protected function configureListFields(ListMapper $listMapper)
{
$listMapper->add('_action', null, [
'actions' => [
'clone' => [
'template' => 'admin/PhotocallListAdmin/__list_action_moderate.html.twig'
]
]
])
;
}
protected function configureRoutes(RouteCollection $collection)
{
if ($this->isChild()) {
$collection->clearExcept(['list', 'moderate']);
$collection->add($collection->getBaseCodeRoute().'.moderate', 'moderate');
return;
}
}
}
So there, I add an action with a template which look like this :
<a class="btn btn-sm" href="{{ admin.generateObjectUrl('moderate', object) }}">
{% if not object.ismoderate %}
Moderate
{% else %}
Undo moderation
{% endif%}
</a>
So the error says that it's unable to find the route admin.photocall|admin.photocall_gallery.moderate. But when I dump the $collection in YAdmin after adding the moderate route, I have two elements :
admin.photocall|admin.photocall_gallery.list (the current page)
admin.photocall|admin.photocall_gallery.moderate
I searched but it looks like that nobody else did this.
Thanks for you help
I write Gaska's comment to close this issue.
I just had to add :
$collection->add('moderate', 'moderate'); and then clear the cache.
Thanks to him

Symfony2 get item type in forms

I have a form, and need the field type in the bindrequest, the method getType dont works fine:
$peticion = $this->getRequest();
if ($peticion->getMethod() == 'POST')
{
$form->bindRequest($peticion);
if ($form->isValid()) {
foreach($form as $key => $per)
$per->getType(); // i want the type of item [text,checkbox,etc] the method getType() dont work
}}
Use this:
foreach($form as $key => $per) {
$per->getConfig()->getType()->getName();
}

Reload or clear data in existing form in Symfony2.1

We upgraded from Symfony 2.0 to 2.1. With 2.0, I used to modify the entity and reload the form like this:
$form->setData($entity);
But this is not allowed anymore with Symfony 2.1 (https://github.com/symfony/symfony/pull/3322). I get the following error:
You cannot change the data of a bound form
Is there a way to rebind the form to the entity/reload the data?
I did something that did the trick… Don't know if it's best way but…
public function contactAction(Request $request){
$task = new myBundle();
$form = $this->createFormBuilder($task)
->add('email', 'text', array('label'=>'E-mail'))
->add('message', 'textarea')
->add('newsletter', 'checkbox', array('label'=>'blabla','required'=>false))
->getForm();
$cloned = clone $form;
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
[... code ...]
$form = $cloned;
}
}
return $this->render(
'myBundle:Default:contact.html.twig',
array('form' => $form->createView())
);
}
By cloning the just instanciated form object, I can switch the «full» one by the empty one, and keep all the params.
And the most obvious way to reset the form after a successful post. Set a "flash", redirect to the form page and display the flash:
public function contactAction(Request $request)
{
$contact = new Contact();
$form = $this->createForm(new ContactType(), $contact);
$form->handleRequest($request);
if ($form->isValid()) {
//...
$session = $this->container->get('session');
$session->getFlashBag()->set('success', 'Your message has been sent.');
return $this->redirect($this->get('router')->generate('contact'));
}
return array(
'form' => $form->createView(),
);
}
And in your twig:
{% if app.session.flashBag.has('success') %}
{{ app.session.flashBag.get('success')[0] }}
{% endif %}
Well, you could create a new instance of the form and re-bind. Seems like overkill, but it would work in a pinch.

Resources