Symfony2 why does forward nest request objects? - symfony

When I do a forward() in a controller, I lose my route and route_parameters.
When I have ParentAction, that does a forward to ChildAction. In Childaction I do return $this->render('myTemplate.html.twig', array()); then the request attributes get nested!
So when the template gets rendered, instead of $request['attributes']['_route_parameters'] I get $request['attributes']['request']['attributes']['_route_parameters'].
Although in ChildAction, when I do a $this->getRequest(); the hierarchie is normal.
Is this a bug, or am I doing something wrong?

The reason is that symfony doesn't assume you have the same route parameters.
So when you forward you need to re-supply the route parameters required by the new route even if they are the same.
(Incidentally you must also provide any query parameters for the new route.)
public function indexAction($param)
{
return $this->forward(
'AppBundle\\Controller\\DefaultController::otherAction',
array("someOtherParam" => $param),
$request->query->all() // Causes query string params to be copied
);
}
// This route has a different parameter.
public function otherAction($someOtherParam) ...

A possible solution would be to pass your Request as second parameter when forwarding.
$response = $this->forward('MyBundle:MyController:myAction', array('request' => $request));
Also, as forward is just a shortcut for core Symfony2 functionality, this may probably help.

In my case following code helped:
$subRequest = $this->container->get('request')->duplicate(
array(),
null,
array('topicId' => $topicId,'_controller' => 'SomeBundle:Topic:close'));
return $this->container->get('http_kernel')
->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
"Topic" is TopicController and "close" is closeAction

Related

parse a URL with a known route to get parameters (reverse/backwards of normal usage)

I hope I may ask this question without going into the details of why I need to do this,
I have a route
/**
* #Route("/edit/{id}", name="my_edit_route")
*/
public function editAction()
{ /* ....... */ }
And now at some other point in time, I have a URL which was generated with this route (I only have the string, no other information is available). The string looks like this
/edit/23
Is there anyway in Symfony2, that I can provide the route name (e.g. "my_edit_route" and this URL string, and have Symfony extract the parameters. (e.g. the id=23)
p.s. I know this could be done with regex or other tools, but my actual routes in my application are more complex.
I am sure this should not be to difficult for Symfony2 to do, as it already does this each time it extracts the parameters from your URL to handle a request.
you no need to route name
$params = $this->get('router')->match('/edit/23');
that return $params as:
array(
'id' => '23',
'_controller' => 'AppBundle:Blog:show',
)

handleRequest($request) does not work for "GET" method in Symfony 2

I am a noobie in Symfony2. The handleRequest() function does not work for "GET" method whereas same code works fine for "POST".
public function addAction(Request $request){
$std = new Student();
$form = $this->createForm(new StudentForm, $std,
array( 'method'=>'GET'));
$form->handleRequest($request);
if($form->isSubmitted()){
$std= $form->getData();
$em= $this->getDoctrine()->getManager();
$em->persist($std);
$em->flush();
return $this->render('target.twig');
}
return $this->render('target twig',
array('newStdForm'=> $form->createView(),));
}
The above code is not working but if I change 'method':'GET' to 'method':'POST', then it works fine.
Specify the form's method in the StudentForm class's buildForm method. Therefore, handleRequest will be able to grab the GET parameters.
class StudentForm
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->setMethod('GET');
}
}
I think it is because in POST requests, parameters are passed in the body of the HTTP request. And that handleRequest looks for those values inside the body of the request. But in a GET request, parameters are passed in the url directly. So I think that is why the handling doesn't work.
Usually we use GET to fetch a page or url and a POST to send info to server.
Are you sure your twig template is correct?
I faced this issue today.
Pierre Roland's answer is partially correct for the current version.
I checked the default "HttpFoundationRequestHandler" which is called in "handleRequest".
An explicit GET form will be considered "submitted" if:
the form has no name (if you use a form class for example).
the request query contains a parameter with the form's name.

Symfony 2 forwarding requests to another controller with changed content

I want to forward a request to another controller using something like this:
$controller_response = $this->forward( 'MyBundle:Clients:getClients' );
That works fine, but I need to updated the request to have different content, I can't work out what method I should be using, the following don't work:
$request->content->set('new content');
$request->set('content', 'new content');
$request->setContent('new content');
Is it even possible to do this? If not I could create a brand new request and add it in as an argument, I'd rather avoid doing that though if possible.
You can alter the Response content with the setContent method.
$response->setContent('<html>Hello</html>');
You can also alter the request
$request->request->set('key', 'value');
$request->query->set('key', 'value');
$request = Request::createFromGlobals();
$newContent = json_decode($request->getContent(), true);
$request->initialize($request->query->all(), $request->request->all(), $request->attributes->all(), $request->cookies->all(), $request->files->all(), $request->server->all(), $newContent);
Unfortunately, there doesn't seem to be a great way of doing it, but this worked for me.

Symfony2 functional testing InvalidArgumentException: The current node list is empty

I get "InvalidArgumentException: The current node list is empty." running functional tests through PHPUnit. Here is test i wrote:
public function testAdd()
{
$client = static::createClientWithAuthentication('main');
$crawler = $client->request('GET', 'en/manage');
$send_button = $crawler->selectButton('submit');
$form = $send_button->form(array(
'PrCompany[email]' => 'test#example.ua',
'PrCompany[first_name]' => 'Anton',
'PrCompany[last_name]' => 'Tverdiuh',
'PrCompany[timezone]' => 'Europe/Amsterdam'
));
$form['PrCompany[companies][1]']->tick();
$client->submit($form);
$this->assertTrue($crawler->filter('html:contains("User is invited")')->count() > 0);
}
You can debug the problem by using var_dump($client->getResponse()->getContent());
Additionally, I think you should write this:
$crawler = $client->submit($form);
Otherwise you'll be testing the response of the first url, before form submission.
I was also struggling with this, and It appeared that the selectButton method triggered this error.
After reading up on the DOM Crawler docs I found that the selectButton method takes the actual button text as a string argument. So if your button is 'submit my form please', that will be your text.
It does take different parameters too, as shown below (taken from the docs)
A selectButton() method is available on the Crawler which returns another
Crawler that matches a button (input[type=submit], input[type=image],
or a button) with the given text.
EDIT
After finally successfully completing the test I would also recommend you follow this example for testing forms:
use Goutte\Client;
$client = new Client();
$crawler = $client->request('GET', 'https://github.com/login');
$form = $crawler->selectButton('Log in')->form();
$form['login'] = 'symfonyfan';
$form['password'] = 'anypass';
$crawler = $client->submit($form);
$this->assertTrue($crawler->filter('html:contains("Welcome Back")')->count() > 0);
The main difference being, I have used the Goutte bundle, which I installed with composer from the packagist (in my case I added "fabpot/goutte": "1.0.*#dev")
As a follow up to what #greg0ire wrote, check to see if
var_dump($client->getResponse()->getContent());
Returns a redirect page instead of the actual content. If so, you can add this:
$client->followRedirects(true);
I had the same problem with Silex application. I was looking for
$buttonCrawler = $crawler->selectButton('input[type="submit"]');
Instead, the correct way to do it is give the value of the button
$buttonCrawler = $crawler->selectButton('value_of_the_button');
For example, in your page:
<form>
...
<input type="submit" value="Click Me">
</form>
And in your tests:
$buttonCrawler = $crawler->selectButton('Click Me');
$form = $buttonCrawler->form();
...
I see question still dont have answer. I had the same problem.
In my case goutte was not able to do this request because input name is changed by javascript on the fly.
When goutte received html it saw one form. And when submitting with pre filled params, form input elements could not be matched by $form->setValues($params) so \InvalidArgumentException was thrown.
Solved by doing request by hand.
// $form->setValues($data);
// $this->getGoutte()->submit($form);
$data = array(
'input_name[key]' => 'value'
);
$this->getGoutte()->request($form->getMethod(), $form->getUri(), $params);
You can try to use Codeception with Symfony2 module. It provides flexible interface to Symfony2 functional tests and has better debugging features.
This error would occur when crawler can't find form element requested; Quite tricky when you are using, for instance, form builder as when run, it will create different input name:
$form = $this-> createFormBuilder($store)
->add('storeNumber','text')
->add('storeName','text')
->add('save', 'submit')
->getForm();
will output field name like:
form_storeNumber
which should be used in test class:
$form=$crawler->selectButton('save')->form();
$form['form_storeNumber']='10';

Symfony2 Functional test: Passing form data directly

I am using phpunit to run functional tests but I am having a problem with a few of the forms. The problem is that phpunit is not aware of JS, and I have a form with a dynamically populated select box that needs jQuery.
So I need to pass the form data directly. The 'book' gives the following example:
// Directly submit a form (but using the Crawler is easier!)
$client->request('POST', '/submit', array('name' => 'Fabien'));
When I used this example the controller didn't receive any of the form data. Intially I saw that passing the array key 'name' wasn't correct in my situation as I needed the form name which was 'timesheet' in my code. So I tried something like:
$client->request('POST', '/timesheet/create', array('timesheet[project]' => '100'));
But this still didn't work. In the controller I tried to understand what was happening and what if anything was being received:
$postData = $request->request->get('timesheet');
$project = $postData['project'];
This didn't work and $project remained empty. However if I used the following code I got the value:
$project = $request->request->get('timesheet[project]');
But clearly that's not what I want. Atleast though I can see that there is some POST data. My last attempt was to try the following in the test method:
$this->crawler = $this->client->request('POST', '/timesheet/create/', array('timesheet' => array(project => '100'));
So I am trying to pass a 'timesheet' array as the first element of the request parameter array. But with this I get the error:
Symfony\Component\Form\Exception\UnexpectedTypeException: Expected argument of type "array", "string" given (uncaught exception) at /mnt/hgfs/pmt/src/vendor/symfony/src/Symfony/Component/Form/Form.php line 489
I would be very happy if someone can expand on what's in the 'book' about how I am supposed to get this working.
Form bind in controller:
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
$postData = $request->request->get('timesheet');
$project = $postData['project'];
$timesheetmanager = $this->get('wlp_pmt.timesheet_db_access');
$timesheetmanager->editTimesheet($timesheet);
return $this->redirect($this->generateUrl('timesheet_list'));
}
}
If you are wanting to know how to inject arrays of POST data using the test client...
In your test method, do something like
$crawler = $client->request('POST', '/foo', array(
'animal_sounds' => array(
'cow' => 'moo',
'duck' => 'quack'
)
); // This would encode to '/foo?animal_sounds%5Bcow%5D=moo&animal_sounds%5Bduck%5D=quack'
$this->assertTrue( ... );
In the controller, you would access your params like this:
$data = $request->request->get('animal_sounds');
$cowNoise = $data['cow'];
$duckNoise = $data['duck'];
Or you could just use the forms API if the test method was injecting valid form data...
do you have a $request parameter in your action?
that was the reason why my request->get() was empty:
//WRONG
public function projectAction()
{
$request = Request::createFromGlobals();
$project = $request->request->get('timesheet[project]');
//$project will be empty
}
//CORRECT
public function projectAction(Request $request)
{
$project = $request->request->get('timesheet[project]');
//$project is not empty
}
see
How do I create a functional test which includes a POST to a page with parameters?
Try to use $form->bind($clientData) instead of $form->bindRequest($request).

Resources