Symfony2 : Two forms in a same page - php

I've got two forms in a same page.
My problem is when I tried to submit a form, it's like it tried to submit the second form below in the page as well.
As follow, you can find my 2 forms :
public function createSuiviForm() {
return $form = $this->createFormBuilder(null)
->add('numero', 'text', array('label' => 'N° : ',
'constraints' => array(
new Assert\NotBlank(array('message' => 'XXXX')),
new Assert\Length(array('min' => 19, 'max' => 19, 'exactMessage' => 'XXX {{ limit }} XXX')))))
->add('xxxx', 'submit')
->getForm();
}
public function createModificationForm() {
return $form = $this->createFormBuilder(null)
->add('modification', 'submit', array('label' => 'XXXXXXXXXXXXXXXXXXXX'))
->getForm();
}
My second form as only a submit button.
I passed them to my render and display them by using :
<div class="well">
<form method="post" action='' {{form_enctype(form)}} >
{{ form_widget(form) }}
<input type="submit" class="btn btn-primary"/>
</form>
<div class='errors'>
{{ form_errors(form) }}
</div>
</div>
'form' is the name of my variable to the first form
and 'update' for my second form.
When I attempted to submit my second form, I need to click twice and finally I get :
"This form should not contain extra fields."
And all non valid input for the remainding form.
I tried to add validation_group to false but to no avail.
I don't understand why I got this error because my forms are not embedded at all
I hope you will understand...

You have to treat the forms separately:
if('POST' === $request->getMethod()) {
if ($request->request->has('form1name')) {
// handle the first form
}
if ($request->request->has('form2name')) {
// handle the second form
}
}
This is perfectly explained in Symfony2 Multiple Forms: Different From Embedded Forms (temporarily unavailable - see below)
Update
As the link provided above is temporarily unavailable, you can see an archive of that resource here.

This did the trick for me in Symfony 3 (should also work for Symfony 2):
$form1 = $this->createForm(
MyFirstFormType::class
);
$form2 = $this->createForm(
MySecondFormType::class
);
if ($request->isMethod('POST')) {
$form1->handleRequest($request);
$form2->handleRequest($request);
if ($form1->isSubmitted()) {
// Handle $form1
} else if ($form2->isSubmitted()) {
// Handle $form2
}
}

The problem is that you have two nameless forms (input names like inputname instead of formname[inputname], and thus when you bind the request to your form and it gets validated it detects some extra fields (the other form) and so it is invalid.
The short-term solution is to create a named builder via the form factory, so instead of:
$form = $this->createFormBuilder(null)
you should use:
$form = $this->get("form.factory")->createNamedBuilder("my_form_name")
The long term solution would be to create your own form classes, that way you can keep your form code separate from the controller.

The two forms will be posted.
Try using:
$this->createNamedBuilder
instead of
$this->createFormBuilder
Then in your controller, locate the form by name:
if ($request->request->has("your form name") {
$form->handleRequest($request);
}

This is how I handle them on my controller :
return $this->render('SgaDemandeBundle:Demande:suivi_avancement.html.twig',
array('form' => $form->createView(),
........
'update' => $formModification->createView()));
This is the html for the second form :
<div class="well">
<form method="post">
<div id="form">
<div>
<button type="submit" id="form_modification"
name="form[modification]">Modification done
</button>
</div>
<input type="hidden" id="form__token" name="form[_token]"
value="fFjgI4ecd1-W70ehmLHmGH7ZmNEHAMqXlY1WrPICtK4">
</div>
</form>
</div>
This is my twig rendered :
<div class="well">
<form method="post" {{form_enctype(update)}} >
{{ form_widget(update) }}
</form>
</div>
<div class="well">
<form method="post" action='' {{form_enctype(form)}} >
{{ form_widget(form) }}
<input type="submit" class="btn btn-primary"/>
</form>
<div class='errors'>
{{ form_errors(form) }}
</div>
</div>
I hope this will help you.

Using Named forms is a viable solution for handling multiple forms, but it can get a little messy, particularly if you're generating forms dynamically.
Another method, as of Symfony 2.3, is to check which submit button was clicked.
For example, assuming that each form has a submit button named 'save':
if ('POST' == $Request->getMethod())
{
$form1->handleRequest($Request);
$form2->handleRequest($Request);
$form3->handleRequest($Request);
if ($form1->get('save')->isClicked() and $form1->isValid())
{
//Do stuff with form1
}
if ($form2->get('save')->isClicked() and $form2->isValid())
{
//Do stuff with form2
}
if ($form3->get('save')->isClicked() and $form3->isValid())
{
//Do stuff with form3
}
}
I believe this has a small amount of additional overhead as compared to the named builder method (due to multiple handleRequest calls), but, in certain cases, it results in cleaner code. Always good to have multiple solutions to choose from. Some of the additional overhead could be alleviated via nested if/else statements, if necessary, but, unless we're talking about dozens of forms per page, the additional overhead is negligible in any case.
Here's an alternate implementation using anonymous functions that minimizes code repetition:
$form1Action = function ($form) use (&$aVar) {
//Do stuff with form1
};
$form2Action = function ($form) use (&$anotherVar) {
//Do stuff with form2
};
$form3Action = function ($form) use (&$yetAnotherVar) {
//Do stuff with form3
};
$forms = [$form1 => $form1Action,
$form2 => $form2Action,
$form3 => $form3Action];
if ('POST' == $Request->getMethod())
{
foreach ($forms as $form => $action)
{
$form->handleRequest($Request);
if ($form->get('save')->isClicked() and $form->isValid())
{
$action($form);
}
}
}

Look at the blockprefix :
public function getBlockPrefix()
{
return 'app_x_form'.$form_id;
}

Related

Symfony : form with GET method, messy URL

I use a search form in Symfony 4.2.5, with GET method.
But the URL is not very... sexy.
I want to get a clean URL.
I have already disabled CSRF protection, and removed the submit from the FormBuilder ( otherwise, the submit button was ALSO on the URL).
The form :
public function searchForm()
{
//Form search creation
$form = $this->createFormBuilder(null, array('csrf_protection' => false))
->setAction($this->generateUrl('page'))
->setMethod('GET')
->add('object', TextType::class)
->getForm();
return $this->render('page.html.twig', ['searchForm' => $searchForm->createView()]);
}
The view :
<form class="search">
{{ form_start(searchForm) }}
{{ form_row(searchForm.object, {'attr' : {'placeholder': "Search..."}}) }}
<button id="searchSubmit" class="btn btn-success">Search </button>
{{ form_end(searchForm) }}
</form>
With this code, I get localhost/page?form[object]=SearchTerm
I know, it is a detail, but I want to get an URL like localhost/page?object=SearchTerm.

How to bind flashSession alert block to current button container in Phalcon framework?

I need to make alert blocks the children of any button container.
In Phalcon framework there's only one instance of flashSession initialization:
<?php $this->flashSession->output(); ?>
This code can be used in specific place of html file. And it's fine if we have only one form with submit button on page - I just put the code inside button block. But If there's more submit buttons, the correct alert placement appears to be impossible. The main question is - is it possible to make flashSession alert to appear only inside block with button which initialized it?
Easy solution would be to use form submit value or add a hidden input to the form.
<button type="submit" name="submit" value="form-1">Submit</button>
or
<input type="hidden" name="submit" value="form-1"/>
And above your form where you want to show flash errors do something like:
<?php if ($this->request->has('submit') && $this->request->getPost('submit') == 'form-1'): ?>
<?php $this->flashSession->output(); ?>
<?php endif; ?>
Try using in your view, just the follow: {{ content() }}
Edit
Forget the flashSession in this case. If you can't create an alert above all your forms:
// In controller
if($this->request->getPost('button1')) {// or just _POST
$this->view->setVar('error1', "You have clicket at button 1");
}
if($this->request->getPost('button2')) {
$this->view->setVar('error2', "You have clicket at button 2");
}
// In view
<form>
<button name="button1"><?php $flash->outputMessage('error', $error1); ?></button>
<button name="button2"><?php $flash->outputMessage('error', $error2); ?></button>
</form>
Here is how like to use. But I don't see any problem to do that in your case:
// In services:
$di->set('flash', function () {
$flash = new FlashDirect( // wherever your classes are, here using bootstrap
array(
'error' => 'alert alert-danger bold',
'success' => 'alert alert-success bold',
'notice' => 'alert alert-info bold',
'warning' => 'alert alert-warning bold'
)
);
return $flash;
});
// in controller
$this->flash->error("THere is something wrong");
$this->flash->success("Nice, it's correct!");
// in view
{{ content() }}
<form name="1"></form>
<form name="2"></form>

Laravel: Repeating Fields (and Field Groups): Form Model Binding

I am building a form in Laravel that deals with an array field in the form of repeatable entities (whether it be single inputs or field groups). I am running into an issue using Form Model Binding when there is either a validation error with a repeated field or a different input in the form.
Right now I'm generating new "instances" of each field by pulling in the view partial with AJAX
# Add Feature
$(document).on 'click', '.js-listing__add-feature', (e) ->
e.preventDefault()
$.ajax
url: dashboard.partials.feature
type: 'GET'
success: (data) ->
$data = $(data)
$('.js-listing__features').append $data
return
return
# Removing features
$(document).on 'click', '.js-listing__remove-feature', (e) ->
e.preventDefault()
$(this).parent('.js-listing__feature-wrapper').remove()
return
So, a user can create new feature inputs on the fly which ultimately combine into an array when saved. The issue becomes when there is a validation issue in the form and we are redirected back. I have not found a way to access the features array in the state it was in (dynamic or not) to spit out what they previously had. In writing this, I guess the issue also becomes clearing out that field if it was the input itself causing the validation issue.
I've searched around in the docs and the 'ole Google for inspiration on this topic, but haven't come across anything. Any nudges in the right direction would be extremely helpful. Thanks as always!
Example of form
#extends('dashboard.master')
#section('content')
<h1>Edit Listing</h1>
#include('dashboard.partials.errors')
{!! Form::model($listing, ['method' => 'PATCH', 'route' => ['dashboard.listings.update', $listing->id], 'class' => 'uk-form']) !!}
<div class="uk-form-row">
{!! Form::label('price', 'Price') !!}
{!! Form::text('price') !!}
</div>
<div class="uk-form-row js-listing__features">
{!! Form::label('features', 'Features') !!}
#if ($listing->features && count($listing->features))
#foreach($listing->features as $key => $feature)
<div class="js-listing__feature-wrapper">
<input type="text" name="features[]" value="{{$feature}}">
<a class="js-listing__add-feature" href="#">+</a>
#if ($key > 0)
<a class="js-listing__remove-feature" href="#">-</a>
#endif
</div>
#endforeach
#else
<div class="js-listing__feature-wrapper">
<input type="text" name="features[]">
<a class="js-listing__add-feature" href="#">+</a>
</div>
#endif
</div>
<div class="uk-form-row">
{!! Form::submit('Update Listing') !!}
</div>
{!! Form::close() !!}
#stop
You'll see my take on what I'd do for the #foreach when I have values to display them when editing the listing. The issue here is not reading the values back (I have set/get attributes working fine for those), but how Form Model Binding works with input arrays so I can still have those values available when they have been dynamically added to the form with AJAX.
I had a similar problem some times ago... My code is surely not very elegant, but it worked ; it may help you to build something...
My trick was to generate a different name for items, distinguish old and new items, count new items:
<input type="text" name="E1-features"> // existing feature #1
<input type="text" name="N1-features"> // new feature #1
<input type="text" name="N3-features"> // new feature #3 (assuming user deleted #2)
<input type="hidden" name="counter" value="3"> // 3 features were added
Server side, the controller distinguishes the existing inputs from the new ones. Here is the code for the new inputs:
Input::flash();
// Existing features
foreach($features as $key => $feature){
if (Input::get('E'.$key.'-features')){
$rules['E'.$key.'-features'] = 'required';
}
}
// New features
for ($i = 1; $i <= Input::get('counter'); $i++) {
if (Input::get('N'.$i.'-features')){
$rules['N'.$i.'-features'] = 'required';
}
}
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()){
return Redirect::to('page')->withErrors($validator)->withInput();
}else{
// data stuff
}

Symfony 2: How to build a really simple form, with just one field and use it in controller

I'm building a get started procedure. It is really very simple: just one field for the email and a submit button.
HOW DOES THE PROCEDURE WORKS
I simply have one controller with two methods: indexAction() and endAction()
The indexAction simply set the route using annotations and displays the twig template with an handmade form:
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
class GetStartedController extends Controller
{
/**
* #Route("getstarted")
* #Template()
*/
public function indexAction()
{
return array(
// ...
);
}
/**
* #Route("getstarted/end", name="getStartedEnd")
* #Template()
*/
public function endAction(Request $request)
{
// ... other code
As you can see, currently the method really does nothing as the form is handmade directly in the twig template.
Here is the code to render the form (handmade in the twig template):
{% extends "::base.html.twig" %}
{% block title %}AppBundle:GetStarted:index{% endblock %}
{% block body %}
<form id="getStarted" action="{{ path('getStartedEnd') }}" method="post">
<div class="form-group">
<input type="email" name="userEmail" class="form-control input-lg" placeholder="Enter your e-mail (es.: your.name#example.com">
</div>
<button type="submit" name="submit" class="btn btn-default btn-lg">Submit</button>
</form>
{% endblock %}
I know Symfony 2 can handle the creation of the form and its rendering on the page, but this is a very simple form and I know it never will be more complicate, so an handmade one is the simplest and fastest solution.
Now, what's the problem? The problem is I don't understand how to get the form and its submitted values in my controller, in the other method, endAction().
The form has as action the path to the other method, endAction() where, in my intentions, I will retrieve the submitted values and do some stuff with them.
Following the instructions in paragraph Using a Form without a Class, i came up with the following code for the method endAction() that, in my intentions, will retrieve the submitted email and do something with it - will create a user, obviously - (note the exit to simply print the results of the if)
/**
* #Route("getstarted/end", name="getStartedEnd")
* #Template()
*/
public function endAction(Request $request)
{
$form = $this->createFormBuilder()
->add('userEmail', 'email')
->add('submit', 'submit')
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
print_r($data);
} else {
echo 'no data submitted';
}
exit;
return array(
// ...
);
}
The problem is that if ($form->isValid()) { ever returns false and I ever obtain as result the printing of echo 'no data submitted';
Clearly there is something wrong with my implementation of the form handling, but... What?
This is my question: what am I doing wrong? Why the form isn't "intercepted" in the controller?
You have two things to do:
First, edit your Form template and use the correct names (name="form[userEmail]" instead of name="userEmail"):
<form name="form" method="post" action="{{ path('getStartedEnd') }}">
<div id="form">
<div>
<label for="form_userEmail" class="required">User email</label>
<input type="email" id="form_userEmail" name="form[userEmail]" required="required"/>
</div>
<div>
<button type="submit" id="form_submit" name="form[submit]">Submit</button>
</div>
</div>
</form>
And second disable the csrf protection of the form:
/**
* #Route("getstarted/end", name="getStartedEnd")
* #Template()
*/
public function endAction(Request $request)
{
$data = array();
$form = $this->createFormBuilder($data, array(
'csrf_protection' => false,
))
->add('userEmail', 'email')
->add('submit', 'submit')
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
print_r($data);
} else {
echo 'no data submitted';
}
exit;
return array(
// ...
);
}
But recommend strongly not to render the form by hand!
It's not more than:
/**
* #Route("getstarted")
* #Template()
*/
public function indexAction()
{
$data = array();
$form = $this->createFormBuilder($data, array(
'action' => $this->generateUrl('getStartedEnd'),
))
->add('userEmail', 'email')
->add('submit', 'submit')
->getForm();
return array(
'form' => $form->createView(),
);
}
and in your template:
{# just this line #}
{{ form(form) }}

can't receive the form request from my controller

I'm trying to do a simple form. The bind between the controller and the view is done. But I can't receive the form data when the user submit.
public function addAction()
{
$router = $this->get('router');
$request = $this->get('request');
$ret = 'not set';
$title = 'not set';
if ($request->getMethod() == 'POST') {
$pictures = $request->files->get('pictures');
$title = $request->request->get('title');
$ret = $this->get('my_project_blog.post_service')
->create($title, $subtitle, $description, $pictures);
}
return $this->render('MyProjectBlogBundle:Default:add.html.twig', array('err' => $ret, 'title' => $title));
}
Now the add.twig.html
<form enctype="multipart/form-data" action="{{ path('my_project_blog_add') }}" method="POST" id="contactform">
<fieldset id="contact_form">
<label for="title">
<input type="text" name="title" id="name" placeholder="Enter A Title">
</label>
<label for="file">
<input name="pictures[]" type='file' multiple='multiple' ></input>
</label>
<input type="submit" class="submit btn btn-default btn-black" id="submit" value="Submit">
</fieldset>
</form>
The result before submit :
Pictures: not set.
Title: not set
After submit :
Pictures: Error pictures count == 0.
Title:
Is there any particular reason, why you are not using Symfony form component?
Not sure how it's possible, but maybe you got wrong request service from container. You should use Request Stack service or add $request as parameter of you action. Just tested following code and everything works correctly.
public function addAction(Request $request)
{
if ($request->getMethod() == 'POST') {
$title = $request->request->get('title');
$files = $request->files->all();
}
}
As xurshid29 mentioned in comment, symfony gives you the opportunity to define forms easier and handle response easier.
// In controller
public function addAction(Request $request)
{
$form = $this->createFormBuilder()
->add('title')
->add('pictures', 'file', [
'multiple' => true, // Since symfony 2.5
])
->add('Submit', 'submit')
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
// Process data from $form->getData()
}
return $this->render('MyProjectBlogBundle:Default:add.html.twig', [
'form' => $form->createView()
]);
}
// In view
{% block content %}
{{ form(form) }}
{% endblock %}

Categories