Symfony2 & phpunit: enable method override - symfony

I just started on a Symfony2 project. The CRUD generation tool created a default controller and functional test, which I'm modifying to suit my needs. The edit-form generated by the controller creates the following HTML:
<form action="/app_dev.php/invoice/7" method="post" >
<input type="hidden" name="_method" value="PUT" />
<!-- ... -->
</form>
I like the approach of overriding the HTTP method, because it enables me to create semantic routes in my application. Now I'm trying to test this form with a functional test, using the following:
$crawler = $client->click($crawler->selectLink('Edit')->link());
$form = $crawler->selectButton('Edit')->form(array(
'_method' => 'PUT',
// ...
));
$client->submit($form);
$this->assertEquals(302, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code for POST /invoice/<id>/edit");
When I execute the tests by running phpunit -c /app, my tests fails because the status code is 405 instead of the expected 302.
With a bit of debugging I found out that the response of is a MethodNotAllowedHttpException. Appearantly, when running the test through PHPUnit, the method overriding (which internally maps a POST request in combination with _method=PUT param to a PUT request) doesn't take place.
That said, my question is: when executing my PHPUnit tests, why doesn't symfony recongize the overwritten method?

The second argument of form method is a http method. So try this:
$form = $crawler->selectButton('Edit')->form(array(
// ...
), 'PUT');

Related

Symfony : Route PUT method

Anyone knows why the PUT method doesn't work using PHP Symfony?
If I replace PUT to POST everything works fine
/**
* #Route("/api/product/update", name="product_udpate", methods = {"PUT"})
*/
i am reading variables like that
$request = Request::createFromGlobals();
echo $request->request->get('name');
error:
No route found for "PUT /api/product/update/23" (404 Not Found)
The problem is you are not creating the route correctly. Basically, you need to add the "id" to the route.
/**
* #Route("/api/product/update/{id}", name="product_udpate", methods = {"PUT"})
*/
public function updateAction(Request $request, $id)
{
// Your logic here
$name = $request->get('name');
}
You got the following error because you have not configured route correctly.
error: No route found for "PUT /api/product/update/23" (404 Not Found)
If you want to add id along with your desire url, you have to define in your route.
Thus, you can update your route:
/**
* #Route("/api/product/update/{id}", name="product_udpate", methods = {"PUT"}, defaults={"id"=null})
*/
As stated in the symfony documentation How to Use HTTP Methods beyond GET and POST in Routes
Unfortunately, life isn't quite this simple, since most browsers do not support sending PUT and DELETE requests via the method attribute in an HTML form. Fortunately, Symfony provides you with a simple way of working around this limitation. By including a _method parameter in the query string or parameters of an HTTP request, Symfony will use this as the method when matching routes
So you have to fake the method like this one :
<form action='your route'>
<input type='hidden' name='_method' value='PUT'>
//do something.......
</form>

phpunit test passes, page load in browser fails in Symfony 2

I am new to phpunit testing and did a very simple test looking for status code.
The test passes when I run:
bin\phpunit -c app src\AppBundle\Tests\Controller\StarLinX\TravelControllerTest.php
PHPUnit 4.6.10 by Sebastian Bergmann and contributors.
Configuration read from C:\PhpstormProjects\dir\app\phpunit.xml.dist
.
Time: 6.03 seconds, Memory: 20.00Mb
OK (1 test, 1 assertion)
But when I load the page in the browser, an exception is thrown rendering the twig file with status code 500.
I thought maybe this was a cache issue, so I cleared cache in --env=dev, prod and test.
How do I troubleshoot this error?
This is my test file:
namespace AppBundle\Tests\Controller\StarLinX;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class TravelControllerTest extends WebTestCase {
public function testGET() {
// Create a new client to browse the application
$client = static::createClient();
// get the page
$crawler = $client->request('GET', '/travel/aaaaa');
$this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code for GET /travel/aaaaa");
}
}
This is the error that is thrown when running in the dev environment:
An exception has been thrown during the rendering of a template ("Notice: Array to string conversion")
So after a little more analysis, I find that the error around {{ weatherInfo }} which should be {{ weatherInfo.now }}. This throws an error when running the development environment. In production, twig simply displays Array.
Is that normal behavior?
As it's written your test only check the error status, the test is not specific enough. What if it displayed another page? The test would still pass.
You should add some other tests in order to ensure that the page is properly displayed:
// …
$this->assertEquals(200, $client->getResponse()->getStatusCode(),
"Unexpected HTTP status code for GET /travel/aaaaa");
// Check that the page has a title.
$this->assertSame(
1,
$crawler->filter('title')->count()
);
// Check that the page has a correct title.
$this->assertSame(
'Travel',
$crawler->filter('title')->text()
);
// Check something in the content
$this->assertSame(
'Hello, World!',
$crawler->filter('body > div#content')->text()
);
If you don't have enough information to debug your code, tou can access to the page content which usually contain the error message:
die($this->client->getResponse()->getContent());

Symfony2 Functonal Tests, query parameters are ignored

How do I send additional parameters with a request in a functional test in Symfony2. I have
$client = static::createClient();
$crawler = $client->request("GET", '/timezones/23.html?X=1', array("rest_auth" => "wrong"));
Both X and rest_auth are missing when the request hits my Symfony2 application. I have tried it with POST too and even with
json_encode(array("rest_auth" => "wrong"))
Nothing seems to work in sending additional query parameters to the request.
It turns out that this will work for the passed params
$req->get("timezone")
But this will not
$_REQUEST['timezone']
$_GET['timezone']

Symfony2 get request in action called in twig

I have a twig template with :
{% render controller('MyBundle:Default:leftside') %}
So i have an action leftside in my controller :
public function leftsideAction(Request $request)
I'm trying, in this action to retrieve GET parameters with :
$request->get('MY_PARAM')
But it's always empty, i think, because i render this action in my template, i can't retrieve all my request.
How can i do that ?
That's totally expected due the concept of request stacks.
The Request supplied to "main" action is, well, MASTER_REQUEST, while those supplied via render controller tag are SUBREQUEST.
You can read more about RequestStack feature here.
Now, in order to have access parameters defined in MASTER_REQUEST you need to get request_stack service and then get the master request. After that, it's business as usual:
public function leftsideAction(Request $request){
$stack = $this->get('request_stack');
$master = $stack->getMasterRequest();
$master->get('MY_PARAM'); // This should work
$request->get('MY_PARAM'); // But this should not
}
Here is the definition of RequestStack class: link

Updating Symfony 2.4 : "Rendering a fragment can only be done when handling a Request."

Since I migrated to Symfony 2.4, I'm getting the following error message :
Rendering a fragment can only be done when handling a Request.
It's happening because, on some pages, I'm rendering some templates with Twig inside some pages which are handled by another older framework, by doing $sf2->container->get('twig')->render("MyBundle::my-template.html.twig");.
So yes, that's right that Symfony 2 isn't handling those requests, but I still want to render those templates with Twig ! Why can't I do that (anymore) ?! And how to fix that ?
Here is the code I'm executing to "boot" SF2 in my older project :
$loader = require_once __DIR__.'/../../../app/bootstrap.php.cache';
Debug::enable();
require_once __DIR__.'/../../../app/AppKernel.php';
$kernel = new AppKernel('bootstrap', true);
$kernel->loadClassCache();
Request::enableHttpMethodParameterOverride();
$request = Request::createFromGlobals();
$kernel->boot();
$kernel->getContainer()->enterScope('request');
$kernel->getContainer()->set('request', $request, 'request');
$this->container = $kernel->getContainer();
EDIT : By the way, it might be related to that : Symfony 2.4 Rendering a controller in TWIG throws "Rendering a fragment can only be done when handling a Request." Exception .
Though, I don't want to downgrade to Symfony 2.3, and deleting the vendor directory didn't fix my problem.
EDIT² : I found out that the problem is because of the new RequestStack.
In HttpKernel, handleRaw(Request $request, $type = self::MASTER_REQUEST) normally push the request to the RequestStack ($this->requestStack->push($request);). So if I add a public method pushRequestStack($request) to the HttpKernel it works.. But how could I do it properly ? I don't find any public method able to get the $requestStack from HttpKernel (so I can push the request externally)..
And I can't use the "normal" method ($kernel->handle($request)) because it would throw some exceptions, for example for the route that doesn't exist, or also for the session that has already been started by PHP..
In conclusion, is there any way to "push" my/any request to the requestStack without completly handling the request ?
You have to push a new Request in the "request_stack" from your Sf2 command.
$this->getContainer()->get('request_stack')->push(Request::createFromGlobals());
I had the same problem and solved this by rebuilding the bootstrap with this command:
php vendor/bundles/Sensio/Bundle/DistributionBundle/Resources/bin/build_bootstrap.php app

Resources