Difficulty in receiving POST parameter in symfony using angular 4 - php

I am using symfony with angular 4. Both are different projects. In symfony I am returning a post variable which is sent via angular. I am just returning the value. Nothing other than that.
But it's returning me blank value. But when I am testing that symfony url using postman it's returning the correct value.
Symfony code
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers:Content-Type,Accept');
return new Response("a-".$request->request->get('roll')."-b"); //gives '**a- -b**' in console. but from postman gives `a-2-b`
Angular code
this.http.post("http://localhost:8000/user/login",{roll:2}).subscribe(
(data) => console.log(data)
);

Your postman's POST request is different than the Angular's POST request
Maybe in Angular you have to add the content-type: application/json header.
To debugging I advice you to compare the two request with logging in symfony code

I'm not 100% sure but I believe you have to JSON.stringify your object on the angular side:
this.http.post("http://localhost:8000/user/login",JSON.stringify({roll:2})).subscribe(
(data) => console.log(data)
);
Next, as said in comments above. Symfony handles x-www-form-urlencoded body by default. For JSON requests you will need to parse it yourself.
e.g.:
$data = json_decode($request->getContent(), true));
Now doing this in every controller action may seem tedious and you'll loose the Symfony Parameter bag.
You can solve this by making a Request listener:
e.g.:
class JsonRequestEventListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (empty($request->getContent()))
{
return;
}
if (!$this->isJsonRequest($request))
{
return;
}
if (!$this->transformJsonBody($request))
{
$response = Response::create('Unable to parse request.', 400);
$event->setResponse($response);
}
}
private function isJsonRequest(Request $request)
{
return 'json' === $request->getContentType();
}
private function transformJsonBody(Request $request)
{
$content = $request->getContent();
if ($content === '')
{
return true;
}
$data = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE)
{
return false;
}
if ($data === null)
{
return true;
}
$request->request->replace($data);
return true;
}
}
What this listener does is the following:
When a request comes in, it checks if there is any content and if the request is json. Then it tries to json_decode the request and inserts it in the request parameter bag. ($request->request ...)
The next thing you will have to do is register this listener:
e.g.:
jsonrequest.listener:
class: AppBundle\EventListener\JsonRequestEventListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 100 }
And from now on, if your request is of type application/json and it contains valid json, you can find it's values in the Request Parameter bag:
public function someAction(Request $request)
{
$request->request->all() //holds the json
}
As I can see from your sample, you set the CORS headers in your controller as well. You could in the same way write a CORS listener to handle this for you.

Related

Symfony-Giving all api responses according to content type

routes.yaml
Whatever the content type is, I want the response to be of that type. How can I do it?
I want it to be response, by content type. I want to do it globally. For example; If the content type is json, the response must be json. If the content type is XML, the response should return XML. or if the content type is xxx the response should return xxx
The system itself should automatically determine the return type according to the content-type. Is this possible in symfony? Because Symfony returns exceptions in xml format by default. If the api user sends the content-type as json and receives an exception, it receives it as xml. I dont want this.
I can write _format json or xml. But I want it to be dynamic
Check how HttpKernel is handling response.
To have response type dynamically respond to client sent Accept header you must write some kind of mapper for it.
You should write some subscriber/listener to transform your response to needed format:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
final class ResponseSubscriber implements EventSubscriberInterface
{
public function __contstruct(private Request $request) {}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => 'onResponse',
];
}
public function onResponse(ResponseEvent $event): void
{
// Skip on internal requests. E.g. redirect
if (!$event->isMainRequest()) {
return;
}
$data = $event->getResponse();
$requestedType = $this->request->headers->has('Accept')
? $this->request->headers->get('Accept')
: 'application/json';
$response = match ($requestedType) {
'application/json' => $this->getJsonResponse($data),
'application/xml' => $this->getXmlResponse($data),
'text/html' => $this->getHtmlResponse($data),
...
default => throw new \InternalException('Request type ' . $requestedType . ' is not supported'), // This better be checked on KernelEvents::REQUEST
}
$event->stopPropagation();
$response->setResponse($response);
}
}
You can use XDebug to do step-debugging and see where your response data is travelling for better understanding of Symfony internals.

POST variables not seen in Symfony's 3.2 Controller

Debugger shows, that there is POST data in request, but I cannot get it by $request->get('foo');
And $request->request->all(); gives array [0].
My Ajax POST request (by AngularJS):
...
$http.post('http://admin/about-company/setJSONObj',
{foo: 'bar'}
);
My controller with debugging info in comments (Symfony 3.2.9):
use Symfony\Component\HttpFoundation\Request;
...
public function updateAction(Request $request)
{
$foo = $request->get('foo'); // null
$requestType = $request->getContentType(); // json
$content = $request->getContent(); // {"foo":"bar"}
I used these approach on Symfony 2.7 project, and it worked fine, but I'm not sure, what's happening in these case?
Also, maybe there is any Symfony framework config variable that tells not to parse POST data, or hides it during caching the request?
For POST request is:
$request->request->get('foo');
Try using FormData for your client-side ajax's call
For example : try something like
var formData = new FormData();
formData.append('foo', 'bar')
$http.post('http://url',
formData
);
Ok, I wasn't paying attention that you used json so,
You wont get the content of $foo in request but you need to json_decode the $content
So by keeping the same way you sent data :
$http.post('http://admin/about-company/setJSONObj',
{foo: 'bar'}
);
You just have to call
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
...
/**
* #Route("/setJSONObj", name="admin_pages_set_ajax_obj")
* #Method("POST")
*/
public function updateAction(Request $request)
{
$foo = $request->get('foo'); // null
$requestType = $request->getContentType(); // json
$content = $request->getContent(); // {"foo":"bar"}
$data = json_decode($content, true);
dump($data['foo']); // "foo"
//And you can know replace the data of the request. So
$request->request->replace($data);
My guess is because symfony need have the 'Content-Type' 'application/x-www-form-urlencoded' but Angular by default have a application/json (so you can override the headers on the $http.post call.
It turned out, my mistake was to expect POST variables in $request->request->all(); while posting JSON: see explanation https://www.toptal.com/....
For creating REST api with Symfony it's normal to use FOSRestBundle: see body listener. So, it solves my problem in elegant way.
Also, #henrily suggested a workaroud somebody can use, but it's just a workaround.

Symfony JsonResponse ignoring defined HTTP code and only responses 200

I'm trying change HTTP code in Response but I always receive 200.
the code below is resumed a lot with both tests:
$app->put('/hdc/v1/pagamento/{pagamento_id}', function($pagamento_id, Request $request) use ($app) {
$pagamento = Pagamento::find($pagamento_id);
return new JsonResponse(["message"=>"error"],400); // returns 200
}
$app->put('/hdc/v1/pagamento/{pagamento_id}', function($pagamento_id, Request $request) use ($app) {
// $pagamento = Pagamento::find($pagamento_id);
return new JsonResponse(["message"=>"error"],400); // returns 400
}
Interacting with the model, JsonResponse is impacted. Why?
Postman screenshot
I think you don't use the good method. See the silex doc :
<?php
$app->put('/hdc/v1/pagamento/{pag_id}', function($pag_id, Request $request) use ($app) {
return $app->json(array('message' => 'error'), 400);
});
Awkward!
My entity file Pagamento had
?>
in the file bottom.
it was sending 200 code header when the Pagamento::find method was called.

Setting status of HTTP request

I tried to set the http status in my custom API when a request is being made.
protected $statusCode = 200;
public function setStatusCode($statusCode) {
$this->statusCode = $statusCode;
return $this;
}
public function respond($data, $headers = []) {
return response()->json($data, $this->getStatusCode(), $headers);
}
public function respondCreated($message) {
return $this->setStatusCode(201)->respond([
'message' => $message
]);
}
$this->respondCreated("Incident was created");
But when I make my server request in POSTMAN, I see status 200 and not 201 as set in the code above and the message is not appearing at all. Do I need to do it differently?
I am using the Laravel framework and implemented the functions by the book "Build APIs you won't hate"
I used the http_response_code() method as suggested and set the code like this:
public function respondCreated($message) {
$this->setStatusCode(201)->respond([
'message' => $message
]);
http_response_code(201);
return $this;
}
When I then return the response code it shows properly, but the POSTMAN Status is still 200?
The helper method by laravel is response() and is described as:
Returning a full Response instance allows you to customize the response's HTTP status code and headers. A Response instance inherits from the Symfony\Component\HttpFoundation\Response class, providing a variety of methods for building HTTP responses:
use Illuminate\Http\Response;
Route::get('home', function () {
return (new Response($content, $status))
->header('Content-Type', $value);
});
For convenience, you may also use the response helper:
Route::get('home', function () {
return response($content, $status)
->header('Content-Type', $value);
});
You can set the HTTP Response Code as stated on PHP documentation.
<?php
// Get the current default response code
var_dump(http_response_code()); // int(200)
// Set our response code
http_response_code(404);
// Get our new response code
var_dump(http_response_code()); // int(404)
?>

Laravel 5.1 doesn't allow you to pass in url params (is there configuration)

Consider the following test:
public function it_should_contain_a_list_of_investors_who_belong_to_one_or_more_investment() {
$this->createInvestment();
$investor = factory(User::class)->create([
'role_id' => 4
]);
$response = $this->actingAs($investor)
->call('GET', 'api/v1/investors?include=investments');
dd(json_decode($response->getContent()));
$this->assertNotEmpty(json_decode($response->getContent()));
}
Now consider the following action this test is calling:
public function getAllInvestorsForCompany($slug)
{
$users = $this->investorEntity->usersForCompany($slug);
$resource = new Collection($users, new InvestorTransformer, 'investor');
dd($_GET);
if (isset($_GET['include'])) {
$usersData = $this->manager->parseIncludes($_GET['include'])->createData($resource)->toArray();
} else {
$usersData = $this->manager->createData($resource)->toArray();
}
return response()->json($usersData);
}
Note the dd, the $_GET returns []
Lets do the same test in the browser:
array:1 [▼
"include" => "investments.offering.company"
]
Ok so in the browser I get back investments.offering.company, because that is what I am passing in as the ?include= But in the test its like laravel ignores the ?include and moves on.
is this a default behaviour of laravel 5.1 tests and if so how do I shut it off?
Don't use $_GET, $_POST, or $_REQUEST in Laravel controllers. You should use the Request facade:
public function getAllInvestorsForCompany($slug)
{
$users = $this->investorEntity->usersForCompany($slug);
$resource = new Collection($users, new InvestorTransformer, 'investor');
if (Request::input('include')) {
$usersData = $this->manager->parseIncludes(Request::input('include'))->createData($resource)->toArray();
} else {
$usersData = $this->manager->createData($resource)->toArray();
}
return response()->json($usersData);
}
When testing, Laravel doesn't actually make HTTP calls - it creates a Request object, and then passes that to the routing controller, which is why those variables aren't actually available.
Using the Request facade also allows you to do things like set a default if the input doesn't exist, and handles multiple input forms (like AngularJS which sends data as JSON in the request body, instead of as POST or GET parameters).
The 3rd argument for call is for parameters.
$response = $this->actingAs($investor)
->call('GET', 'api/v1/investors', ['include' => 'investments']);

Categories