How do I make my forms modular in Symfony? - php

I have 10 forms in a single page (they are in tabbed pages).
My controller function is massive, in an effort to make the forms modular, I was planning to just show the forms in my controller function and do the POST to their correspondent controller.
Some of these 10 forms are used in other pages, so by changing the action url, it makes it easier to manage them all in separate controllers.
But then, how am I going to show the form errors and prevent the form to reset if it was invalid?
I could achieve this if embedded controllers could redirect. Am I missing another option?
Symfony2 has to have a reason not to allow redirects in embedded controllers, but I wonder why.
Third edit:
Images of the actual forms: http://imgur.com/sIx3sgs,tnZkvqM,ZAP950s,hk45oTW#0
Image 1: Step1 (can only be created once)
Image 2: Step5 (can be created multiple times) You can see the create form and the edit form
Image 3: Step5 ui forms are slided up.
Image 4: Step6 same as Step1
These forms are not required to go in order, the user can create and save step6 without ever needing to create step1.
Second edit:
This isn't a multiple step form. I named them Step1, Step2, etc. for convenience.
Edit:
This is what I have:
class DefaultController extends Controller{
public function processAction(){
$request = $this->getRequest();
/*** FORM 1 ****/
$entity = new Step1();
$form1 = $this->createForm(new Step1Type(), $entity);
if ($request->getMethod() == 'POST'){
$form1->bindRequest($request);
if($form1->isValid()){
return $this->redirect($this->generateUrl('some_link'));
}
}
/***/
//Do the same for other forms
return array(
'form1' => $form1->createView()
//[...to form10]
);
}
}
This is what I would like to have: (I would do it with embedded controllers but you can't redirect)
class DefaultController extends Controller{
public function processAction(){
$request = $this->getRequest();
/*** FORM 1 ****/
$entity = new Step1();
$form1 = $this->createForm(new Step1Type(), $entity);
//change $form1 action url to point to Step1Controller->createAction()
/***/
//Do the same for other forms
return array(
'form1' => $form1->createView()
//[...to form10]
);
}
}
class Step1Controller extends Controller{
public function createAction(){
$request = $this->getRequest();
$entity = new Step1();
$form = $this->createForm(new Step1Type(), $entity);
$form->bindRequest($request);
if($form->isValid()){
//save entity
return $this->redirect($this->generateUrl($getRedirectLinkFromForm));
}
return $this->redirect($this->generateUrl('some_other_link'));
}
}

I came up with 4 solutions that I could think of, in order from best to worst:
AJAX
No redirect in embedded controllers
Save in session posted data / form errors in case form is not valid
Modify Symfony2 internals to allow redirect from an embedded controller
AJAX
This is actually the best as it reduces whole page requests and it's highly modular.
You would render all 10 embedded controllers but each submit button will instead do an ajax post pointed to the correspondant controller, which will only process the correspondent forms and it will only return the view of that specific form.
And you don't need to get fancy on your javascript, as you only need to make the AJAX call when submit is clicked and load the response to the div where the form is.
No redirect in embedded controllers
The problem with this is that if the user presses F5, it will resend the POST data.
Save in session posted data / form errors in case form is not valid
and Modify Symfony2 internals to allow redirect from an embedded controller
These 2 options are less desirable as it creates complexity.

Related

Alternative way of header() to stop page refresh form submit

I am working on a web application with a form submit function. People fill in their info and submit the form. But I have the problem that people can resubmit the form when filled in with F5(Page refresh). Now i have used header() before to fix this.
I don't really like header(), because some users need to submit the form with different data... And header() does not work properly sometimes for me.
I am programming in PHP with the symfony framework, Maybe the framework has some kind of function for this, I can not really find out.
I hope people know good working alternative ways of header(). To achieve this.
You might do something like this with use of redirectToRoute() method from your Controller:
class DefaultController extends Controller
{
public function someAction(Request $request): Response
{
# create form and handle request
$form = $this->createForm(YourFormType::class, $data);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
/* some logic here */
# generate flash message
$this->addFlash('success', 'Your flash message.');
# refresh page or redirect to other page
return $this->redirectToRoute('your_route_name');
}
/* some other logic */
}
}
More examples on Symfony Docs.
You can redirect User to the same page using "RedirectResponse" after submitting data, then if User click F5 it will not send another POST Request. You can also use Ajax call for submitting data.

How do I redirect back when validation fails in Laravel 5.4?

I'm working on a Laravel 5.4 project and have multiple pages with the same url e.g. www.blahblah.com/order/verify/{encryption_key}
My routs are:
Route::get('/order/verify/{encrypted_key}','PinVerificationController#init');
Route::post('/order/verify/{encrypted_key}','PinVerificationController#pinValidation');
The flow is they land on a page first where they enter their phone number, then they go to a second page where they have to enter a pin code. I validate if the pin code is a number, if it isn't then I redirect back with an error message. But they're being redirected to the first page instead.
If the validation fails i'm routing back. I'm doing
return \Redirect::back()->withInput()->withErrors($validator);
but this is routing to the GET page instead of the POST page.
Why is this happening?
UPDATE #1
public function init(){
$country_extensions = appUtils::getCountryExtensionDropdown();
//TODO
$country_iso_code = "1-US";
$parameters = compact( 'country_extensions','country_iso_code' );
return view('/pages/choose_phone_verify_method',$parameters);
}
private function pinValidation(Request $request){
$validator = \Validator::make($request->all(), [
'pin_number' => 'required|numeric'
]);
if ($validator->fails()) {
return \Redirect::back()->withInput()->withErrors($validator);
}
}
I don't know if you make your validation in a controller or in a request. But as I can see you redirect back(), and it must be from your controller.
My suggestion is you use the formRequest class instead of the validator in your controller.
You see, the getRedirectUrl() method of the FormRequest class, tests for some special properties on the class, and if it doesn't find any value, it falls back to a redirect, using the Illuminate\Routing\UrlGenerator::previous() generated URL. Those properties that the FormRequest checks, are the redirection options you have.
Now you have two options of changing them, either globally in every form request you make, by putting the property in the abstract class App\Http\Requests\Request that every form request class inherits from. Or, in particular, form classes, by simply putting them in the form class itself.
And these are all the options you have for custom redirections :
protected $redirect; // A simple URL. ex: google.com
protected $redirectRoute; // A route name to redirect to.
protected $redirectAction; // A controller action to redirect to.
But if you insist do the validation in your controller you can write an if statement. so that if the validator fails it redirect to a specific path like page 2 path in this situation. like this code below:
if ($validator->fails()) {
return redirect('path to page 2')->withInput()->withErrors($validator);
}
Or you can redirect to route name:
if ($validator->fails()) {
return redirect(route('route name'))->withInput()->withErrors($validator);
}
Wouldn't it be easier to just handle the post request in the same method (init()).
That way you would need to redirect, but just display the errors.
And the user could easily correct his errors (since the form could be filled out, and it's automatically shown again) and submit the form again.

Twig and controller in Symfony

I have a form made in Twig and I want to pass the values of this form to my database, so how do I pass the Twig values to the controller?
In the first photo is the form that I created in twig
Formulario twing
In the second picture is the controller (the entities, and the connection with the database with doctrine orm is complete too), it is only necessary to know how to take the form data and to pass to the controller
Controller
When form submits you will get a POST request to the same URL that it was built in.
Stick to this tutorial:
http://symfony.com/doc/current/forms.html
If you create a form with html and not symfony form you must manually handle form and other step.
you must submit your form to you controller route that seems is cadastrarAction().
Don't forget to declare route with POST method for cadastrarAction().
In controller you can access posted data from request argument like this.
use Symfony\Component\HttpFoundation\Request;
class FooController extends Controller {
cadastrarAction(Request $request)
{
// Find what you exactly want. if you want to get query use this
$request->getQueryString()
}
}
This is bad practice that declare form manually. you must use FormType for easily handle and persist form to database. use symfony official site for more information about form and entity.
In official documentation you can find flow, how create entity, form, twig template and set data in database.
If you need get data from form use this:
if ($form->isSubmitted() && $form->isValid()) {
// -------- Hear you take data --------
$ideda = $form->get('ideda')->getData();
$email = $form->get('email')->getData();
$telemovel = $form->get('telemovel')->getData();
// ---------- Hear you set data's ---------
$usuario -> setIdeda($ideda);
$usuario -> setEmail ($email );
$usuario -> setTelemovel($telemovel);
}):

Multiple Symfony Forms Added Accross Many Pages

I've created a new form type for a simple one field newsletter signup form. What's the best way to embed this across different pages, multiple times on my site?
I could create a new instance of this form with a different name in every controller to show in my twig templates, but this seems a little messy. Ideally I'd like to be able to add more than one form on a page, and add them easily to a template.
I've tried {% render controller('MyBundle:EmailSubscribe:subscribe') %} but this doesn't work with multiple inserts, and I'm sure there's a better way anyway. Any ideas?
May be with Twig extension? This kind of task is the next one in my current project (e-commerce project where I need to display multiple addToCart forms on category pages), I'm also planning to do this with Twig function, for example (I did'nt test it yet):
private $formFactory;
public function __construct(FormFactoryInterface $formFactory)
{
$this->formFactory = $formFactory;
}
public function getFunctions() {
return [
new \Twig_SimpleFunction('newsletter_form', [$this, 'getNewsletterForm']),
];
}
public function getNewsletterForm()
{
$formFactory = $this->formFactory;
$form = $formFactory->create('my_form_alias', null_OR_data);
return $form->createView();
}
and use it in template as newsletter_form
Im a fan of xurshid29's Answer
What I do is simplay render the controller like your answer suggests. But that causes only one form to get processed, and leaves the unprocessed forms with errors like Cannot extra fields and so forth.
The Solution:
Pass the request from the original controller.
In Controller
function myAction(Request $request){
$this->render('path:to:template.html.twig', array('request'=>$request));
}
And In my Twig Template I simply pass the request to that controller.
{% render(controller('path:to:controller',{request:request} %}
And now you can create a base template for that form and include it in any template all across the site. This was especially useful for me with FOSUserBundle!
There may be some kind of twig function app.request ? I've been too lazy to try look it up. But in that case you don't have to pass request from original controller.
Extra tips on this subject
What I like to do that allows me to insert my forms anywhere for rapid dev is to build a controller/template for each form, like a "Join our Mailing List" or something.
Controller - The goal of this controller is to process the form and output "success" or show the form.
public function emailSubscribeFormAction(Request $request){
$email = new Email() //Presumed Entity for form
$form = /* Create Form Here */->getForm();
$form->handleRequest($request);
if($form->isValid()){
$form = "success"; //Change form into a string
}
$this->render('path:to:email-form.html.twig', array(
'form'=>($form=="success") ? $form : $form->createView();, // Either pass success or the form view
'request'=>$request
}
Twig email-form.html.twig
{% if form == "success" %}
<h4 id="form-success">Thanks for joining!</h4>
<script>
window.location = "#form-success" /*Bring the user back to the success message*/
</script>
{% else %}
<h4>Come on, Join Our Mailing List!</h4>
{{ form(form) }}
{% endif %}
Now you can render that controller anywhere and not have to worry about another thing about it.
Can work with multiple forms but I may have read there are drawbacks to rendering controllers like this?
If I understood your problem correctly you want to embed the same form multiple times in a page and this is to be done for multiple pages. So lets say that for page_1 you want to embed the subscribe_form N times you can obviously create a form and add this form type N times like this:
$form = $this->createFormBuilder()
->add('subscribe1',new SubscribeType())
->add('subscribe2',new SubscribeType())
...
->add('subscribeN',new SubscribeType())
->getForm();
So you can also do this in a for loop like this:
$form = $this->createFormBuilder();
for ($i = 1; $i<N+1; $i++){
$formName = 'subscribe'.$i;
$form->add($formName,new SubscribeType());
}
$form->getForm();
Now you can include this code in a controller as you said and pass the variable N as argument in each of your twig templates. Moreover, you can do this through javascript especially if you want to add these forms(SubscribeType) dynamically using allow_add option in your form. Documentation about this is here: http://symfony.com/doc/current/cookbook/form/form_collections.html#allowing-new-tags-with-the-prototype.
Hope that helps otherwise inform me if I misunderstood your problem.

Using several modules in the same view

I'm developing a web application with Zend Framework 1.12, which is something new to me, and I'm not sure about the way to do something I want to.
EDIT: When I talk about Module, I mean Controller, sorry for that, I still mistake the terms ...
On my home page, the module Index, I made what I wanted to do with it, created several actions and all the stuff, but I'd like to add a search engine I'll make myself.
The problem is that I'd like to create the search engine as a separate module named Search, for example, but put the SearchForm in the home page. Hitting submit would send the datas from the form to the Search module.
I don't quite understand how to do that without having to go to /search to access my form and every associated actions.
Do I have to use a View Helper ?
Also, the searchForm in the front page would be some sort of QuicKSearch and accessing /search would show a more elaborated form for the research.
Can someone explain me how to access the searchForm from the Index module or redirect me to the part of the documentation talking about that ? My research are unsuccessful and Google doesn't help me either.
EDIT: When I talk about Module, I mean Controller, sorry for that, I still mistake the terms ...
First of all, build the searchform as viewHelper, then you can reuse it in several views.
The action attribute in form snippet set to searchModule/controller/action.
Additionaly make research about viewHelpers and Forms in Zend Documentation.
I actually prefer to do this as a an action helper and then just use a standard placeholder view helper to present the search form.
let me demonstrate:
the actual action helper just initiates a form and prepares it for display. I'll leave the form structure to you.
//the action helper
//Just fill in the args for the form to be displayed
class NameSpace_Controller_Action_Helper_Search extends Zend_Controller_Action_Helper_Abstract
{
public function direct($action, $label = null, $placeHolder = null)
{
$form = new Application_Form_Search();
//set the action
$form->setAction($action);
//set the submit button text
$form->search->setLabel($label);
//set the hint text displayed in the form window
$form->query->setAttribs(array('placeholder' => $placeHolder,
'size' => 27,
));
return $form;
}
}
I put the helper in the predispatch method of the controller so that each action in the controller can use the search form with having to build it in every page.
//to use the helper in your controller
class IndexController extends Zend_Controller_Action
{
public function preDispatch()
{
//setup action helper and assign it to a placeholder
$this->_helper->layout()->search = $this->_helper->search(
'/index/display', 'Search Collection!', 'Title');
}
//in your view script
<?php echo $this->layout()->search ?>
I like to put the placeholder in my master layout.phtml so that any time I populate the placeholder it will display. Now all you have to do is style it however you want.
Remember: As with any html form the action parameter is just a url so any valid url can be assigned to the form action. In this example I used the /controller/action parameters, but there are many other ways to pass a url to the form. The url helper comes to mind as good way to do it.
url($urlOptions, $name, $reset, $encode): Creates a URL string based
on a named route. $urlOptions should be an associative array of
key/value pairs used by the particular route.

Categories