Symfony 3 Extending Forms for Caching - php

So I have a Symfony 3 app that is embedded in a website (.jsp). I have to redirect back to the webpage and not let it fall through to the view as it will display the app out of the website.
So in order to save the form states to give the user feedback on the redirect I have to cache the form errors and values into Redis and retrieve them again applying them back to the form to display.
I'm doing this at the top and bottom of every controller action that utilizes a form.
The problem is this is repeated code and bloats out my controllers. Is there anyway to extend Symfony form or maybe attach to a form event of some kind so that the following happens:
Submit form.
Check if form is valid.
If form is not valid then the parent form class isValid method or form event caches to Redis.
Perform redirect.
When the form is created again check if it has an item in cache corresponding to it's name.
Retrieve that cache and apply back to the form object.
Here is my current controller with the caching I want to abstract.
/**
* #param Request $request
* #return Response
*
* #Route("/signin", name="signin.user")
*/
public function signInAction(Request $request)
{
// Instantiate the form with the Authentication entity.
$authentication = new Authentication();
$headers = $this->getAppHeaders();
$form = $this->createForm(SignInFormType::class, $authentication, [
'config' => $this->getCustomization(true)[$this->getActionName(true)]['form'] ?? [],
'action' => '/apps/' . $headers['schemaId'] . '/' . $headers['schemaVersion'] . $this->generateUrl('signin.user'),
'method' => 'POST',
]);
$redisService = $this->get('cache.redis_service');
$serializer = $this->getSerializer();
// Retrieve from cache and apply back to the form before purging cache.
if ($cachedErrors = $redisService->hget('forms:' . $form->getName(), 'errors')) {
$cachedErrors = $serializer->decode($cachedErrors, 'json');
FormUtil::applyErrorsToForm($form, $cachedErrors);
// Clear cache
$redisService->hDel('forms:' . $form->getName(), 'errors');
}
// Handle the form request which also handle errors.
$form->handleRequest($request);
// If the form is submitted and contains no errors then process.
if ($form->isSubmitted()) {
if ($form->isValid()) {
// Try and perform authentication
$authService = $this->get('core.authentication_service');
if ($response = $authService->performLogin($authentication)) {
return $response;
}
// User could not be authenticated return response.
$formError = new FormError($authService->getResponseBody());
$form->addError($formError);
}
if (count($form->getErrors())) {
$this->flashMessage('danger', [
'title' => 'There has been a problem',
'template' => 'AppBundle:error:sign-in-error.html.twig'
]);
}
// Cache form errors before redirect.
$redisService->hset('forms:' . $form->getName(), 'errors', FormUtil::getFormErrorsToSerialized($form));
return $this->redirect($this->getFullPresencePath() . '/sign_in.page');
}
return $this->render(
'AppBundle::signin.html.twig',
[
'form' => $form->createView(),
'config' => $this->get('core.application_service')->getAppCustomization()
]
);
}
Any help would be greatly appreciated :)

Related

Trying to fill input with object on create page

I'm new to symfony and I'm trying to learn but I'm stuck at the moment :
I'm trying to create a form with input already filled, let me explain :
I've got a Session table, linked with an antibiotic (when I create a session, I choose an Antibiotic), Antibiotic table is linked to Result table.
Like this : Tables
What I'm trying to do, is when I click on Valid Result on Result list page, it creates a form of a new result with antibiotic input already filled (because a Session has one Antibiotic)
This is what i've got now (where 6 is the Antibiotic ID from the Session) :
Add Results page
I've tried to look on how edit pages works with no results.
Thanks
/**
* #Route("/session/resultat/{id}", name="addResultat")
*/
public function voirAction($id, Request $request){
//Find the Session object
$em=$this->getDoctrine()->getManager();
$session=$em->getRepository("AppBundle:Session")
->find($id);
if ($session==null)
{
throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException("Ouste !");
}
$resultat = new Resultat();
$form = $this->createForm('AppBundle\Form\ResultatType', $resultat);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($resultat);
$em->flush();
return $this->redirectToRoute('resultat_show', array('id' => $resultat->getId()));
}
return $this->render('resultat/addresultats.html.twig', array(
'resultat' => $resultat,
'session' => $session,
'form' => $form->createView(),
));
}
I got it to work finally thanks to ccKep :
Just set my object before creating the form like this :
So after I initialised my new "resultat", i can set his "antibiotique" with setter method and I get it from the $session with the getter method
$resultat->setAntibiotique($session->getAntibiotique());

Is this the correct way to handle forms in two pages?

I have an index page which contains a simple form; if the form validation fails the index page is reloaded with errors if not then the action related to the page forwards the request to another action related to page success. The success page uses the form submitted to create a list from DB. Once we are on success page we have another form similar to the first one which the user can use to modify the list on the page. Both forms have same fields.
index page action:
class DefaultController extends Controller {
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request) {
$event = new Event();
$form = $this->createForm(EventForm::class, $event);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
// Do some minor modification to the form data
$event->setDate($party->getDate()->modify('+12 hours'));
$cityName = strtolower($event->getPlace()['city']);
// We know the form data is valid, forward it to the action which will use it to query DB
return $this->forward('AppBundle:Frontend/Default:eventsList', array('request' => $request));
}
// If validation fails, reload the index page with the errors
return $this->render('default/frontend/index.html.twig', array('form' => $form->createView()));
}
success page action (where the form data gets forwarded)
/**
* #Route("/success", name="eventList")
*/
public function eventsListAction(Request $request) {
$event = new Party();
// Create a form in the second page and set its action to be the first page, otherwise form submition triggers the FIRST action related to page index and not the one related to page success
$form = $this->createForm(EventForm::class, $event, array('action' => $this->generateUrl('eventList')));
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$event->setPlace($form["place"]->getData());
$event->setTitle($form["title"]->getData());
$repository = $this->getDoctrine()
->getRepository('AppBundle:Event');
// ....
// Create a list of events using the data from DB
// ....
return $this->render('default/frontend/success.html.twig',
array('events' => $events, 'form' => $form->createView())
);
}
return $this->render('default/frontend/success.html.twig', array('form' => $form->createView()));
}
Although the above implementation "works" I have a couple of issues:
When I submit the first form the url stays the same, that of the first page like:
[HOST]/app_dev.php?place=London&Date=......
But if I submit the second form the URL is correctly:
[HOST]/app_dev.php/success?place=London&date=.....
The implementation feels hacky to me, is there a cleaner way to achieve this?
When the form is submitted, then it's porcessed with the same controller and action. You have to process the submited data and then redirect to success page.
So in your example:
if($form->isSubmitted() && $form->isValid()) {
...
...
return $this->redirectToRoute('eventList');
}
And if you need to transfer posted data from one "page" to another, then you have to store it in session $this->get('session')->set('name', val); and then retrieve data from session in eventList action by $this->get('session')->get('name');
More info how to handle sessions in Symfony: https://symfony.com/doc/current/controller.html#the-request-object-as-a-controller-argument

Passing Success or Failure For Laravel 4 Validation

I am in the middle of working with my create a user form inside of my restful user controller. While the user is on the form I have it currently posting to the store method. When the form is submitted to the method I want it to validate the user input and then redirect back to the create method for either displaying a success message or an array of error messages if there was a failure in the data validation. Can someone point out what is needed to help me redirect to the create method with either a success message or error message based off the validation test.
/**
* Store a newly created user in storage.
*
* #return Response
*/
public function store()
{
$input = Input::all();
$validation = Validator::make($input, $rules);
if ($validation->fails())
{
$messages = $validator->all();
}
else
{
User::create([
'username' => $input['username'],
'email_address' => $input['email_address'],
'password' => $input['password'],
'role_id' => $input['role']
]);
}
return Redirect::to('users/create');
}
you could use either
$messages = $validator->messages();
or
$failed = $validator->failed();
to retrieve error messages. If you want show 'flash' message, you can use 'with' method in redirection.
Redirect::to('user/currentroute')->with('message', 'Something wrong');
for more details, you should read in documentation

How to connect a form into another phtml view in Zend 2 framework

I am new at Zend2:
I have a form, and in the first stage I create a new ViewModel and return it:
return new ViewModel(array('form' => $form, 'messages' => $messages));
In the post stage when the data comes back from the browser, how can I connect this same form to a new View (which has the same elements maybe less, maybe more) or create another form and rassign it the old form's data and relate it to a new view to show?
Any help would be appreciated.
EDIT:
I tried to do the following:
$form->setAttribute('action', $this->url('auth/index/login-post.phtml'));
But still shows the old one.
When I do this:
return $this->redirect()->toRoute('auth/default', array('controller' => 'index', 'action' => 'login-post'));
I get error page: The requested controller was unable to dispatch the request.
When I get the post of the request I need to load another view, I mean how do I specify which view is connected to which form?
The forms do not themselves have any knowledge of the view. If you wish to change the view after completing the form submission; where this new view provides perhaps a different form, this is something that should be done within the controller.
A (non-working) example with a few options on how a different view could be returned.
class FooController extends AbstractActionController
{
public function getFooForm()
{
return $this->getServiceLocator()->get('Form\Foo');
}
public function getBarForm()
{
return $this->getServiceLocator()->get('Form\Bar')
}
public function fooAction()
{
$request = $this->getRequest();
$form = $this->getFooForm();
if ($request->isPost()) {
$form->setData($request->getPost());
// Is the posted form vaild
if ($form->isValid()) {
// Forms validated data
$data = $form->getData();
// Now there are a few options
// 1. Return a new view with the data
$view = new ViewModel($data);
$view->setTemplate('path/to/file');
return $view;
// OR Option 2 - Redirect
return $this->redirect()->toRoute('bar', $someRouteParams);
// Option 3 - Dispatch a new controller action
// and then return it's view model/response
// We can also pass on the posted data so the controller
// action that is dispathed will already have our POSTed data in the
// request
$request->setPost(new \Zend\Stdlib\Parameters($data));
return $this->forward()->dispatch('App\Controller\Foo', array('action' => 'bar'));
}
}
// Render default foo.phtml or use $view->setTemplate('path/to/view')
// and render the form, which will post back to itself (fooAction)
return new ViewModel(array('form' => $form));
}
public function barAction()
{
$request = $this->getRequest();
$form = $this->getBarForm();
if ($request->isPost()) {
$form->setData($request->getPost());
// ....
}
// Renders the bar.phtml view
return $this->viewModel(array('form' => $form));
}
}
From what I understand form your question, you would need to be using option 3 as the new view should populate a second form with it's already validated data.
If you are referring to something like an edit view then you just need to bind your object to the form.
$form->bind($yourObject);
http://zf2.readthedocs.org/en/latest/modules/zend.form.quick-start.html#binding-an-object
Otherwise you can make the form post to any controller action using by setting it:
$form->setAttribute('action', $this->url('contact/process'));
Maybe post what code you have and more specifics and I'm sure you will get some more detailed answers

Symfony2 form submitting on page refresh

I have a form in Symfony2 framework. On successful submission of the page it renders another twig template file and returns the values by passing the parameters in an array. But after submission, if I refresh the page, again it is submitting the form and the table entry is created. Here is the code that is executed after submission in the controller,
$this->get('session')->setFlash('info', $this->get('translator')->trans('flash.marca'));
return $this->render('NewBundle:Backend:marca.html.twig', array(
'active' => 1,
'marca' => $marca,
'data' => $dataCamp,
'dataMarca' => $this->getMarcas($admin->getId()),
'admin' => $admin,
));
I want the form to be redirected to the twig files mentioned there with the parameters and the alert message mentioned above. But I don't want the form to be submitted on page refresh.
Thanks
This worked for me:
return $this->redirectToRoute("route_name");
You should save submitted data in session and redirect user. Then you will be able to refresh page as much as you want without additional submission.
Example code - your action algorithm should be similar:
...
/**
* #Route("/add" , name="acme_app_entity_add")
*/
public function addAction()
{
$entity = new Entity();
$form = $this->createForm(new EntityType(), $entity);
$session = $this->get('session');
// Check if data already was submitted and validated
if ($session->has('submittedData')) {
$submittedData = $session->get('submittedData');
// There you can remove saved data from session or not and leave it for addition request like save entity in DB
// $session->remove('submittedData');
// There your second template
return $this->render('AcmeAppBundle:Entity:preview.html.twig', array(
'submittedData' => $submittedData
// other data which you need in this template
));
}
if ($request->isMethod('POST')) {
$form->bindRequest($request);
if ($form->isValid()) {
$this->get('session')->setFlash('success', 'Provided data is valid.');
// Data is valid so save it in session for another request
$session->set('submittedData', $form->getData()); // in this point may be you need serialize saved data, depends of your requirements
// Redirect user to this action again
return $this->redirect($this->generateUrl('acme_app_entity_add'));
} else {
// provide form errors in session storage
$this->get('session')->setFlash('error', $form->getErrorsAsString());
}
}
return $this->render('AcmeAppBundle:Entity:add.html.twig', array(
'form' => $form->createView()
));
}
Redirect to same page is preventing additional data submission. So lean of this example modify your action and you will be fine.
Also instead save data in session you can pass it through redirect request. But I think this approach is more difficult.
Save your data (session/db/wherever you want it saved)
redirect to a new action, retreiving the new data in that action, and rendering the template
this way, refreshing the new action, will only refresh the template, as the saving of your data happened in the previous action
understand ?
so basically replace your
return $this->render....
by
return $this->redirect($this->generateUrl('ROUTE_TO_NEW_ACTION')));
and in this new action, you put your
return $this->render....

Categories