Render controller and get form errors from child - symfony

I have a template where I render a widget which contains a form:
{{ render(controller('ApplicationDemoBundle:Demo:newWidget', {'demo' : entity })) }}
The newWidgetAction calls a createAction:
public function createAction(Request $request)
{
$entity = new Demo();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('demo_show', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
)
// Something like this would be awesome with passing the form containing errors with
// return $this->redirect($this->getRequest()->headers->get('referer'));
}
Imagine the submitted form (user acts in show theme) produces an error. This would make a return to the newWidget template which does not display the full layout.
My question is now: What is the right way to pass the errors from the child controller (newWidget) to the main template (show)?, without modifying the showActions function parameter to pass the formerrors over there.
There is a similar thread to this question: Symfony2 render and form
In this case where sessions used but I'm more than curious if this is the way to go.

The problem is that each fragment (a sub-controller) uses a virtual request. This is to protect the original request from modification by possibly unexpected forwards, and a fragment is essentially a forward taking place during a rendering stage.
It is possible to access the top level request using:
$this->container->get('request'); then handing the request with the form in the fragment, but this may get very confusing very quickly if you are using multiple forms per page.
My strategy is to follow a convention that limits the number of validated form on a page to just one. Any other forms don't require validation, or it is otherwise impossible for a form to be submitted incorrectly (hacked forms would throw server side exceptions, but the user should only see those if they are being naughty).
Try to structure your template inheritance to accommodate navigation to forms, while always showing most of the same layouts and data. You can do this by expanding the use of fragments which gives the bonus of separating your display logic.

Related

Symfony2 - Sonata Admin - Edit/Create "return to list" action

I have a Sonata admin for an entity with many elements that naturally spans multiple pages in the list view.
What I'd like to do is make it so after editing, or creating a new entity, redirect the user to the appropriate page which displays the edited entity (as opposed to going back to the list's first page which is the default behavior of the sonata admin). The dafult behavior is ok when there are only 1 or 2 pages but when you have tens or even hundreds of pages, navigating back to the correct page becomes quite tedious.
So my question is what is the appropriate way to make this happen?
I'm thinking that it would involve customizing the admin controller for the entity but I'm not sure what the right extension points are. And also, how to utilize the paginator to obtain the correct page to navigate back to.
Another potential hack would be to capture the query parameters state when navigating from the list view to the edit, and then returning the user to the same URL. This won't work correctly for creating new items.
There's also the matter of the state of filters when navigating from the list view (if the user had sorted and/or filtered the list before navigating to the edit page).
I know I'm late but this can be useful for someone else...
Here is the way I've made it, by overriding AdminBundle CRUDController:
<?php
namespace MyProject\AdminBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as BaseController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
class CRUDController extends BaseController
{
protected function redirectTo($object, Request $request = null)
{
$response = parent::redirectTo($object, $request);
if (null !== $request->get('btn_update_and_list') || null !== $request->get('btn_create_and_list')) {
$url = $this->admin->generateUrl('list');
$last_list = $this->get('session')->get('last_list');
if(strstr($last_list['uri'], $url) && !empty($last_list['filters'])) {
$response = new RedirectResponse($this->admin->generateUrl(
'list',
array('filter' => $last_list['filters'])
));
}
}
return $response;
}
public function listAction(Request $request = null)
{
$uri_parts = explode('?', $request->getUri(), 2);
$filters = $this->admin->getFilterParameters();
$this->get('session')->set('last_list', array('uri' => $uri_parts[0], 'filters' => $filters));
$response = parent::listAction($request);
return $response;
}
}
I am having the same problem, I was thinking of passing a variable in the route to the edit page, thus giving you where the request for the edit originated from, then you could redirect to the originating page given the variable.

whats the point of using the #Method annotation

Route Method¶ There is a shortcut #Method annotation to specify the
HTTP method allowed for the route. To use it, import the Method
annotation namespace:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
/**
* #Route("/blog")
*/
class PostController extends Controller
{
/**
* #Route("/edit/{id}")
* #Method({"GET", "POST"})
*/
public function editAction($id)
{
}
}
I've seen a lot of developers limiting the Method to either only GET or POST,
but since the controller allows both by default, why do developers choose to restrict it to only one method? is this some kind of security measure? and if yes what kind of attacks would that protect you from?
First, there are several method available following the spec, not only GET and POST
I don't think this is a security reason, it's more a matter of respecting standards (e.g REST methods).
I personally use different methods for several behaviours. For me, there's the action of SEEING the edition, and APPLYING the edition.
That's two different behaviours for a single URL. Even if the response at the end will tends not to change, the behaviour at controller level is different.
I think this is a matter of personnal preference, I like rather see
/**
* #Route("/edit")
* #Method({"GET"})
* #Template
*/
public function editAction()
{
$obj = new Foo;
$obj->setBaz($this->container->getParameter('default_baz'));
$type = new FooType;
$form = $this->createForm($type, $obj, array(
'action' => $this->generateUrl('acme_foo_bar_doedit'),
'method' => 'PUT'
));
return array(
'form' => $form->createView()
);
}
It's pretty clear what it does. It just instanciates the form you need, no user input are processed.
Now, you can add your action to process the edition by adding a second method
/**
* #Route("/edit")
* #Method({"PUT"})
* #Template("AcmeFooBundle:Bar:edit.html.twig")
*/
public function doEditAction(Request $request)
{
$obj = new Foo;
$type = new FooType;
$form = $this->createForm($type, $obj, array(
'action' => $this->generateUrl('acme_foo_bar_doedit'),
'method' => 'PUT'
));
$form->handleRequest($request);
if ($form->isValid()) {
// Play with $obj
}
return array(
'form' => $form->createView()
);
}
Easy too, and can easily be used elsewhere in your application (rather than in the default edition page)
I personally ALWAYS define a request method (POST, GET, PUT etc etc). I think that (especially with RESTful API's) this is transparent. It can protect you against some attacks, because you are limiting the methods that can be used. It also makes sense, because if you login you POST data and don't GET it and if you request an article, you DO want to GET it :) See what I mean? Only the 'it makes it more transparent' has catched me already. I'm hooked to always defining the methods, whether it's only for clarity or anything else.
edit: Haven't seen the other answer yet (must've been added while I pressed the submit button :) )
There are a lots of reasons to choose between POST, GET, PUT and DELETE methods (or Http verbs). first of all there are some limitations in using GET method for example you can not include large amount of data in URL query string or MULTI-PART forms for uploading files.
And there are many security considerations about using POST and GET methods and I don't even know where to start from. You can Google that. And finally in RESTful web services convention CRUD (Create/Retrieve/Update/Delete) operations are mapped to Http methods (POST/GET/PUT/DELETE). For example:
path: /student/{id}, method GET returns a student
path: /student, method POST creates a student
path: /student, method PUT updates student info
One of the most important security reasons would be that URLs are usually logged in ISPs, Apache web server and network devices(firewalls, ...) and if you include sensitive data such as session ids, ... your data will be stored in plain text in so many places that you have no idea about. Also i recommend have a look at OWASP top 10.

Symfony2: KnpPaginator only show the first page with POST form

I'm using this bundle in an application. The controller is the typical that shows a search form, take the response and process it (an example):
public function indexAction()
{
$request = $this->getRequest();
$example = new Example();
$form = $this->createForm(new ExampleFindType(), $example, array(
'action' => $this->generateUrl('example_find'),
'method' => 'POST',
));
$form->handleRequest($request);
if ($form->isValid())
{
$em = $this->getDoctrine()->getManager();
$examples = $em->getRepository('ApplicationExampleBundle:Example')
->find_by_fields($example);
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$examples,
$this->get('request')->query->get('p', 1),
20
);
return $this->render('ApplicationExampleBundle:Default:searchResults.html.twig',
array('pagination' => $pagination));
}
return $this->render('ApplicationExampleBundle:Default:index.html.twig',
array('form' => $form->createView(),
));
}
When I perform the search I see the results list and the paginator correctly. The problem appears when I press the link to the next page. The link id generated well, with the URL ending with "?p=2" but it seems that the form POST data is not resent because it sent me to the search form page ($form->isValid() is false).
If I change the form method from POST to GET and pass the parameters in the URL:
$form = $this->createForm(new ExampleFindType(), $example, array(
'action' => $this->generateUrl('example_find'),
'method' => 'GET',
));
the paginator works perfect.
Am I doing something wrong? Is possible to use a POST form?
I've searched an answer but all the KnpPagintor controller examples I've seen don't generate the query with forms, and this question hasn't helped me.
Thanks.
You shouldn't use POST method to get data.
Otherwise, if you need to use the POST method then you need the data in the session. However it's difficult to build a nice user experience while it just makes more sense to use a GET method.
You can find an extensive documentation about HTTP on MDN.
A GET method should be used when you request data.
A POST method should be used when you save data (like saving a comment into a database) or other data manipulation.
Google uses a GET on their own search page.
https://www.google.com/#q=symfony&start=10
q is what I searched for and start is the paginator value. They probably use an offset instead of a page number to avoid calculating the offset (faster and less expensive).

Symfony2, $form->bind() not calling adder methods of entity

I'm sorry for my bad English, I'm not a native speaker. Feel free to correct my text if needed.
The question is really simple (it's in the end of this text), but I've written a rationale with some research and tests.
Rationale
If you want, you can skip this rationale and jump directly to the question itself.
I've been trying for several hours on trying to get a ManyToMany relationship to persist from the inverse side using doctrine:generate:entities and doctrine:generate:crud on Symfony2 console.
From the owning side, the relationship is saved in the database with the generated crud out of the box, but not from the inverse side (this is expected: http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#owning-side-and-inverse-side)
What I want is to make it work from the inverse side as well without changing the autogenerated controllers; I'd like to change only the model (entity).
The easy way would be to add a couple of custom code lines to the controller:
// Controller that works the way I want
// Controller/AlunoController.php
...
public function createAction(Request $request)
{
$entity = new Aluno();
$form = $this->createForm(new AlunoType(), $entity);
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
// begin custom code
foreach ($entity->getResponsaveis() as $responsavel) {
$responsavel->addAluno($entity);
}
// end custom code
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('aluno_edit', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
...
The previous code works, but it is not what I want, because those custom lines of code refer to the relationship logic, and should be centralized in the Entity itself (if not, it would have to be duplicated all over the controllers that update this entity).
So, what I did next was to change my adders and removers in the Entity file to execute that required logic, adding the code to automatically update both inverse and owning side (as recommended in http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#picking-owning-and-inverse-side and https://stackoverflow.com/a/7045693/1501575)
// Entity/Aluno.php
...
/**
* Add responsaveis
*
* #param \MyBundle\Entity\Responsavel $responsaveis
* #return Aluno
*/
public function addResponsavei(\MyBundle\Entity\Responsavel $responsaveis)
{
/* begin custom code */
//var_dump('addResponsavei');
$responsavel->addAluno($this);
/* end custom code */
$this->responsaveis[] = $responsaveis;
return $this;
}
...
This should work, because it does work when the same code is in the controller, but it actually does not.
The problem is that the method Aluno##addResponsavei() is never being called when $form->bind($request) runs in the controller (first code sample up there) (I realized this with that var_dump() line. I've also put var_dumps in some other getters and those other methods were called as normal).
So, all the regular setters are indeed called inside $form->bind($request), but not this one. This is weird, because the method names were autogenerated by `doctrine:generate:entities', which I assumed would make $form->bind() know how to call all the setters, getters and adders.
Question
Why is $form->bind() not calling the adder method (Aluno##addResponsavei())?
Is there a special naming convention not followed by doctrine:generate:crud that is preventing the method from being found and executed?
Solution
Thanks for the comment from user1452962 and later the answer from Elnur Abdurrakhimov, I've got it to work and it is actually pretty simple.
All I had to do is to add the option 'by_reference' to false in the properties that hold the inverse side of the relationship, and suddenly addResponsavei() began to be called.
// Form/AlunoType.php
...
$builder
->add('nome')
->add('cidade_natal')
->add('nascimento')
->add('email')
->add('endereco')
->add('nome_sem_acento')
->add('data_hora_cadastro')
->add('responsaveis', null, array('by_reference' => false))
->add('turmas', null, array('by_reference' => false))
...
This way, the relationship logic is gone from the controller, and that what I was looking for. Thank you guys.
You need to set the by_reference option to false in order for adders to be called.

Preventing form_token from rendering in Drupal "GET" forms

Drupal inserts a form_token as a hidden field when it renders forms. The form_token is then checked on form submission to prevent cross-site request forgery attacks. The form data that is submitted is guaranteed to have come from the original form rendered by Drupal.
However, forms using the "GET" method shouldn't need this token. All it does is lengthen and uglify the resulting URL.
Is there any way of suppressing it?
Yes, there is a way, but use it consciously (see warning below):
If you create the form yourself, adding
$form['#token'] = FALSE;
to the form definition array should prevent a token from being generated in the first place.
If you are dealing with an existing form, you can bypass the token validation process by unsetting the '#token' element on hook_form_alter:
// Example for removal of token validation from login (NOTE: BAD IDEA!)
function yourmodule_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'user_login_block') {
unset($form['#token']);
}
}
Warning: Given your question, I think there is a slight misconception concerning the difference (better, the lack of a difference) between GET and POST requests.
... on forms using the "GET" method
shouldn't need this token. All it does
is lengthen and uglify the resulting
URL.
This is wrong! GET and POST are just two different, but mostly equivalent methods of transmitting data from the client to the server. Since POST is better suited to transfer large amounts of data (or difficult formatted data), it is the established standard for submitting forms, but it is in no way safer/unsafer or more/less secure than GET requests. Both type of requests can be tampered with by malicious users in the same ways, hence both types should use the same protection mechanisms.
With a GET request, the token does exactly the same as with a POST request - it proves to the server that the submitted data comes from the same Browser on the same machine as the request he build the form for! So you should only remove it if you are sure that the request can not be misused via XSRF.
This worked for me. I had to unset all the form api elements and set the #token property to false. Notice the after_build function for unsetting the other properties.
function mymodule_form(&$form_state){
$form['name'] = array(
'#type' => 'textfield',
'#title' => 'name',
'#value' => 'name',
);
$form['#method'] = 'get';
$form['#action'] = url('someurl');
$form['submit'] = array('#type' => 'submit', '#value' => 'go');
$form['#token'] = false;
$form['#after_build'] = array('mymodule_unset_default_form_elements');
return $form;
}
function mymodule_unset_default_form_elements($form){
unset($form['#build_id'], $form['form_build_id'], $form['form_id']);
return $form;
}
The site I work on uses the Drupal 6 form API for custom search forms, so by removing the token and build id we were able to cache the results in memcache. Now we've moved to Acquia hosting, it's cached using Varnish.
To remove the form_token and form_build_id from your form and submit it as a GET request, use the following method:
<?php
function module_example_form($form_state, $form_id = NULL) {
// Form root settings.
$form = array();
// Set the submission callback for this form.
$form['#submit'][] = __FUNCTION__ . '_submit';
// Set the request method for this form to GET instead of the default
// of POST.
$form['#method'] = 'get';
// Remove unique form token so request can be cached. This is accompanied by
// code in hook_form_alter to ignore the token and remove the build_id.
$form['#token'] = FALSE;
// Submit button.
$form['go'] = array(
'#type' => 'submit',
'#value' => t('Go!'),
);
return $form;
}
/**
* Implements hook_form_alter().
*/
function module_form_alter(&$form, $form_state, $form_id) {
// Changes to the 'module_example_form' form.
if ($form_id == 'module_example_form') {
// Unset the hidden token field and form_build_id field.
unset($form['#token'], $form['form_build_id'], $form['#build_id']);
}
}
?>
I find that simply throwing away the CSRF token is not an option. We solved it using hook_theme_registry_alter() to overwrite the Drupal core theme_hidden() function so that the hidden form element 'form_token' is rendered as an <esi /> tag. The tag will cause Varnish to make a call to a PHP file which we allow to pass through the cache. This file will calculate the proper form token for the current user and will then output the HTML code for the hidden field. You can calculate this token without a Drupal bootstrap, but you will need a single DB query to fetch the *drupal_private_key* for your site, which is stored in the variable table.

Resources