I am trying to create a form using Symfony 2.5 using this tutorial, but this tutorial is using old version of Symfony. I can get the form to display and created the entity as well however I am now working on submitting the form. Following is the code from tutorial and this code is inside default controller contactAction
public function contactAction()
{
$enquiry = new Enquiry();
$form = $this->createForm(new EnquiryType(), $enquiry);
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
// Perform some action, such as sending an email
// Redirect - This is important to prevent users re-posting
// the form if they refresh the page
return $this->redirect($this->generateUrl('BloggerBlogBundle_contact'));
}
}
return $this->render('BloggerBlogBundle:Page:contact.html.twig', array(
'form' => $form->createView()
));
}
My main concerns is in the following section of above code
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
As you will notice it is using getRequest() which has been depricated and then my IDE is telling me buildRequest method cannot be found.
I will really appreciate if someone call push me towards the right path of converting the contactAction for symfony verion 2.5, I will really appreciate it.
Declare the action like this:
public function contactAction(Request $request)
{
...
}
And import:
use Symfony\Component\HttpFoundation\Request;
And you will have the request inside your action, so you can remove this line:
$request = $this->getRequest();
Hi there are a few deprecated calls also I would really recommend going to the cookbook in Symfony. But anyway this below will help.
namespace myproject/mybundle/controller;
use Symfony\Component\HttpFoundation\Request;
Class Act //that is me ;) {
/**
* #Route("/contact", name="_lalalalala")
* #Template()
*/
public function contactAction(Request $request){
$enquiry = new Enquiry();
$form = $this->createForm(new EnquiryType(), $enquiry);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($enquiry);
$em->flush();
return $this->redirect($this->generateUrl('BloggerBlogBundle_contact'));
}
return ['form' => $form->createView()];
}
}
You can shorten this code even further by using symfony services container to inject your form. I would recommend reading this it is pretty awesome. As you can re use forms anywhere :)
You can change the getMethod to isMethod like
if ($request->isMethod('POST'))
And then your can submit the request data to the form using submit like
$form->submit($request->request->get($form->getName()));
Alternatively you can use the handleRequest method that will handle the above 2 method in one go and then you can move on with the rest of your action like
$form->handleRequest($request);
if ($form->isValid())
.. etc
To retrieve the request object, you have two possibilities. Either let Symfony pass the request as an argument to your controller action ...
public function contactAction(Request $request)
{
// ...
}
... or get the request object from the container.
$request = $this->get('request');
For the binding of the request to the form, the manual states the following:
Passing the Request directly to submit() still works, but is deprecated and will be removed in Symfony 3.0. You should use the method handleRequest() instead.
This way, the request binding part would look similar to this:
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
// do something
}
}
Related
How can I handle request data from the $_POST data itself. I mean if I try to handle the form like this: $form->handleRequest($request);, Symfony would try to get the data from $_POST['form_classname'], but I want to fill my form class straight from base $_POST variables.
Actually I want to handle the information from the outer site. And I have to develop something like an API. But without authorization, tokens, etc...
So I decided to build the form with some properties I need. After validation the form might do some logic.
Here is an example of $_POST I have to handle
Function=TransResponse&RESULT=0&RC=00&AUTHCODE=745113
As you can see, there is no form name in request. The $form->handleRequest($request); works only if the request was like an
[form_name][Function]=TransResponse&[form_name][RESULT]=0&[form_name][RC]=00&[form_name][AUTHCODE]=745113
But I can't change the request format.
Just put in your form class
/** #inheritdoc */
function getBlockPrefix() {
return '';
}
Here is the information about this method Documentation
Use
$this->get('form.factory')->createNamed('')
$this->get('form.factory.)->createNamedBuilder('')
to create a Form or FormBuilder respectively that uses the whole $_POST/$_GET array for its parameters.
Example:
/**
* #Route("/testRoute")
* #param Request $request
* #return Response
*/
public function testAction(Request $request): Response
{
$form = $this->get('form.factory')->createNamedBuilder('', FormType::class, null, ['csrf_protection' => false])
->add('text', TextType::class)
->setMethod('GET')
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
return new Response($form['text']->getData());
}
return new Response('Submit me');
}
ok, I have submitted serialized data via AJAX using JQuery into a function within my Symfony 2 (2.7.10) app.
I am now having a few issues with trying to bind that data to a new entity and save that entity.
Here is what I have done to date:
public function AJAX_SaveTestAction(Request $request) {
// $GetFormData = $request->get('Data'); <- This is a jquery serialized data
$NewData = new TestInfo(); <- this is the entity I want to save
$form = $this->createForm(new InfoType(), $NewData);
$form->handleRequest($request);
if ( $request->isMethod('POST') ) {
$form->bind( $request->get('Data') );
$em = $this->getDoctrine()->getManager();
$em->persist( $NewData ); <- not saving jquery returns 500 server error?
$em->flush();
}
die(); <- this is just here for testing, I will want to return a json message or something else!
}
All help most welcome.
Thanks.
Don't use form->handleRequest and form->bind
form->bind was the equivalent of form->handleRequest before Symfony 2.6.
If you are using Symfony 2.3 use form->bind
If you are using Symfony 2.6+ use form->handleRequest
Your Controller should look like :
public function AJAX_SaveTestAction(Request $request)
{
$NewData = new TestInfo();
$form = $this->createForm(new InfoType(), $NewData);
$form->handleRequest($request);
if($form->isSubmitted()){ // if you also want to check if your entity is valid, just replace it with "if($form->isValid()){"
$em = $this->getDoctrine()->getManager();
$em->persist( $NewData );
$em->flush();
return new Response('It works');
}
return new Response('error');
}
But your JS should send a POST request to this route (with post parameters).
May be with that :
http://api.jquery.com/jquery.post/
To bind correctly your request data to your entity using handleRequest, Symfony expects an array with your form name as key in your POSTed data.
If you did not override method getName of your form, it is defined by default by "underscoring" your Form class name. So it should be info_type here.
TL;DR: You should submit your data as ['info_type' => [ YOUR DATA... ]] instead of just [ YOUR DATA... ]
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
Exists some way in Symfony 2 to generate CSRF token at each rendering of form?
In my controller I tried something like this:
$request = $this->get('request');
if ($request->getMethod() != 'POST') {
$csrf = $this->get('form.csrf_provider');
$date= new \DateTime("now");
$this->date = $date->format('Y-m-d H:i:s');
$token = $csrf->generateCsrfToken($this->date);
} elseif($request->getMethod() == "POST") {
$csrf = $this->get('form.csrf_provider');
$token = $csrf->generateCsrfToken($this->date);
}
$form = $this->createFormBuilder()
....
->add('_token','hidden',array(
'data' => $token
))->getForm();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
DO something
}
}
All the time is in my request right token hash. But after bindRequest its change to default hash generated from security string in parameters.ini and isValid method returns certainly FALSE response. Exists some way, how to adjust it?
EDIT
In response to theunraveler answer, I edited my controller and create my CSRF provider, but still im geting "The CSRF token is invalid. Please try to resubmit the form" error. My CSRF provider looks like this:
namespace My\Namespace;
use Symfony\Component\HttpFoundation\Session;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
class MyCsrfProvider implements CsrfProviderInterface
{
protected $session;
protected $secret;
protected $datetime;
public function __construct(Session $session, $secret)
{
$this->secret = $secret;
$this->datetime = new \DateTime('now');
$this->session = $session;
}
public function generateCsrfToken($intention)
{
return sha1($this->secret.$intention.$this->datetime->format('YmdHis').$this->getSessionId());
}
public function isCsrfTokenValid($intention, $token)
{
return $token === $this->generateCsrfToken($intention);
}
protected function getSessionId()
{
return $this->session->getId();
}
}
Than I add to config.yml service class:
services:
form.csrf_provider:
class: My\Namespace\MyCsrfProvider
arguments: [ #session, %kernel.secret% ]
And Controller I change To this:
//any form without _token field
$form = $this->createFormBuilder()
->add('field1')
->add('field2')->getForm()
if ($this->getRequest()->getMethod() == 'POST') {
$request = $this->getRequest();
$form->bindRequest($request);
if ($form->isValid()) {
Do something
return $this->redirect($this->generateUrl('somewhere'));
}
}
Cant be problem in seconds in my hash? Becouse if I remove seconds from datetime format, it works, but with 1 min expiration and its not a good solution. I think, the reason is, that the time was changed.
Symfony's Form component generates the CSRF token in a Form event. See the class Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener on how the existing implementation works. That means when you call bindRequest() (or bind() in Symfony 2.2+) on your form, that form event gets called and overwrites the token you declared.
The correct way to define a different token is to create a CsrfProvider, which would basically be a class that you write that implements Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface. Check out Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider for an example of how to implement this.
Once you write your class, in your app/config/parameters.yml (or another config file, it doesn't matter where), set the form.csrf_provider.class parameter to the name of your class.
After all of that, you should be able to remove all of the custom code you wrote from your controller, and just use the form system as though nothing were any different (i.e., remove the ->add('_token')... parts).
Hope that helps!
Are you aware of form_rest? Add this to the bottom of your form to have it automatically generate your CSRF token:
{{ form_rest(form) }}
I am trying to add a simple form to allow my users to edit their profile. My problem is:
Since the entity "linked" to the form is the same as the current user object ($user === $entity, see below), if the form validation fails, then the view is rendered with the modified user object (ie. with values of the non-valid form).
Here my (classic) controller:
public function profileAction()
{
$em = $this->getDoctrine()->getEntityManager();
$user = $this->get('security.context')->getToken()->getUser();
$entity = $em->getRepository('AcmeSecurityBundle:User')->find($user->id);
// $user === $entity => true
$form = $this->createForm(new ProfileType(), $entity);
$request = $this->getRequest();
if ($request->getMethod() === 'POST')
{
$form->bindRequest($request);
if ($form->isValid()) {
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('profile'));
}
}
return $this->render('AcmeSecurityBundle:User:profile.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
So I wondered how to have two distincts objects $user and $entity. I used clone() and it worked well for the view rendering part (the $user object was not modified), but it created a new record in database instead of updating the old one.
PS: I know I should use FOSUserBundle. But I would really like to understand my mistake here :)
I used the same solution as FOSUserBundle, which is calling $em->refresh() on my entity when the form validation fails:
public function profileAction()
{
$em = $this->getDoctrine()->getEntityManager();
$user = $this->get('security.context')->getToken()->getUser();
$entity = $em->getRepository('AcmeSecurityBundle:User')->find($user->id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$form = $this->createForm(new ProfileType(), $entity);
$request = $this->getRequest();
if ($request->getMethod() === 'POST')
{
$form->bindRequest($request);
if ($form->isValid()) {
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('profile'));
}
$em->refresh($user); // Add this line
}
return $this->render('AcmeSecurityBundle:User:profile.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
Note that if you are using what is called a "virtual" field in "How to handle File Uploads with Doctrine" (in my case "picture_file" you will need to clear it by hand:
$em->refresh($user);
$user->picture_file = null; // here
One approach would be to always redirect:
if ($form->isValid()) {
$em->persist($entity);
$em->flush();
}
return $this->redirect($this->generateUrl('profile'));
Of course you lose error messages and changes.
Another approach would be to define an entity manager just for your UserProvider. $user would no longer be the same as $entity. Bit of extra overhead but it certainly makes the problem go and would prevent similar interactions with other forms that might modify all or part of an user entity.
In a similar fashion, you reduce the overhead by creating an entity manager just for your profile form. With this method, the overhead would only be incurred when editing the profile.
Finally, you could ask yourself if it really mattered that the display data was not quite right in this particular case. Would it really bother anything? Would anyone notice but you?
See How to work with Multiple Entity Managers in Symfony Cookbook
Another idea is to clone your user entity in your user provider. This will divorce it from the entity manager.
You could also use $entityManager->detach($user); to remove the user from the entity manager.
And why is the token user an entity anyways? Consider making a completely independent User class with minimal information pulled from the database by your user provider. That is what I do.