I have a gcloud function in PHP that receives a json object.
Inside my function, I read the json object like this: file_get_contents('php://input').
This works for me when the json object that I receive in the function is less than 16KB, but when the json object is greater than 16KB it returns empty.
There is no code, neither client- nor server-side ...
You might be sending as enctype="multipart/form-data"
? https://php.net/manual/en/wrappers.php.php#wrappers.php.input
While file_get_contents('php://input') is questionable by itself.
Instead use the default method signature, which provides ServerRequestInterface $request:
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
...
public static function on_https( ServerRequestInterface $request ): ResponseInterface {
$params = $request->getQueryParams();
$body = $request->getBody();
$body->rewind(); // or $body->seek(0);
$bodyText = $body->getContents();
$data = json_decode($bodyText);
return (new Response())
->withHeader('Content-Type', 'application/json; charset=utf-8')
->withBody(Utils::streamFor(json_encode($data)))
->withStatus(200);
}
Just recently pushed a template composer project to GitHub, based on the documentation. Not exactly certain about $body->rewind(), that's only required for streams (it may return string already). This requires "guzzlehttp/psr7": "^2.1.0". Also see: What is PSR-7 and how to use it?
Related
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.
Currently we're doing unit testing in Laravel, and I just noticed my colleague this line below (its working fine though). I look for a documentation in Laravel about this but I can't find it. It seems all we're just focusing on getting the request input values in the documentation.
use Illuminate\Http\Request;
// ...more code here
$request = Request::create('/users/all', 'GET');
I just wanna ask how to pass a parameter using the above code line? And someone can give me a documentation about that.
Check the create function at here:
https://github.com/symfony/symfony/blob/5cfe73d95419bac1ffdddc4603db7266e428b454/src/Symfony/Component/HttpFoundation/Request.php#L336
As you can see, you can pass parameteres as third argument:
Example:
Request::create('/users/all', 'GET', ['username' => 'admin']);
Note: Laravel Request extends Symfony Base Request class
The 3rd argument to create is for an array of parameters. Illuminate\Http\Request extends Symfony\Component\HttpFoundation\Request which defines the create method:
public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null)
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.
I am using the lucadegasperi/oauth2-server-laravel package for OAuth in my API (a Laravel 4.1.* app) and it provides a filter to easily verify authorization before running a route like this:
Route::group(array('prefix' => 'api', 'before' => 'oauth:auth'), function() {
// My API Routes
});
It returns a JSON response that doesn't follow the format I am using in my API and would like to change it to be consistent. So I created a filter in filters.php and set it to run as an after filter.
Route::group(array('prefix' => 'core', 'before' => 'oauth:auth', 'after' => 'oauth.cleanresponse'), function() {
// My API Routes
)};
And the filter:
/**
* OAuth Package returns JSON response in custom format that is not consistent with
* Core API output. We need to alter the output to fit the standard response.
*/
Route::filter('oauth.cleanresponse', function($request, $response) {
if ($response instanceof Illuminate\Http\JsonResponse)
{
$responseData = $response->getData();
if (isset($responseData->error_message));
{
$newResponse = new API\ApiResponse();
$newResponse->setError($responseData->error_message);
$newResponse->setCode($responseData->status);
return Response::json($newResponse->error(), $responseData->status);
}
}
});
The filter runs fine, and I can var_dump() out my changes just before returning them.
But the value returned from the API call in the response is not my new value, it's still the original value created in the before filter by the oauth library.
TL;DR; Why does the response from an after filter not override the response from a before filter and how can I work around this issue?
NOTE: I do not want to edit the OAuth package because then anytime I do a composer update it might overwrite my changes.
EDIT
A closer inspection of the Laravel router (Illuminate/Routing/Router.php) has this:
/**
* Dispatch the request to a route and return the response.
*
* #param \Illuminate\Http\Request $request
* #return mixed
*/
public function dispatchToRoute(Request $request)
{
$route = $this->findRoute($request);
$this->events->fire('router.matched', array($route, $request));
// Once we have successfully matched the incoming request to a given route we
// can call the before filters on that route. This works similar to global
// filters in that if a response is returned we will not call the route.
$response = $this->callRouteBefore($route, $request);
if (is_null($response))
{
$response = $route->run($request);
}
$response = $this->prepareResponse($request, $response);
// After we have a prepared response from the route or filter we will call to
// the "after" filters to do any last minute processing on this request or
// response object before the response is returned back to the consumer.
$this->callRouteAfter($route, $request, $response);
return $response;
}
It appears to block dispatching a request if the before filter returns a result and then it calls the after filter. The problem is it doesn't capture the response! It seems that this:
$this->callRouteAfter($route, $request, $response);
Should be something like this (although this specifically doesn't work):
$response = $this->callRouteAfter($route, $request, $response);
Can anyone think of a way around this?
You have to set the response in the current instance using
$json = 'prepare the json here';
// set and return response with new data
return $response->setContent($json);
"After" filters have 3 parameters and not 2: route, request and response ("before" filters only have 2: route and request, because there is no response at that stage).
In your code, your after filter function has only two parameters, so your $request contains a Route, and your $response contains a Request. You need to add $route as first parameter.
After that, you can then call $response->header() and $response->setContent() to modify the generated response.
"After" filters cannot return a new $response, they can only modify the one passed to them.
Sample working code:
Route::filter('api_headers', function($route, $request, $response) {
$response->header('Access-Control-Allow-Origin', '*');
$response->header('P3P', 'CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA"');
});
Route::group(array('prefix'=>'api', 'after'=>'api_headers'), function()
{
// ...
});
I have a symfony website, and Im trying to do some unit testing. I have this kind of test where I try to submit something:
<?php
namespace Acme\AcmeBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class HomeControllerTest extends WebTestCase {
public function testrandomeThings() {
$client = static::createClient();
$crawler = $client->request(
'POST',
'/',
array(
"shopNumber" => 0099,
"cardNumber" => 231,
"cardPIN" => "adasd"),
array(),
array());
}
but I dont think that the data Im sending is being received in the controler:
class HomeController extends Controller
{
public function indexAction()
{
var_dump($_POST);
die;
return $this->render('AcmeBundle:Home:index.html.twig');
}
}
the var_dump is actually returning me an empty array.
What am I missing to send information through my POST request?
$_POST is a variable filled by PHP and the symfony request is only created from this globals if called directly over http. The symfony crawler doesn't make a real request, it creates a request from the parameters supplied in your $client->request and executes it. You need to access this stuff via the Request object. Never use $_POST, $_GET, etc. directly.
use Symfony\Component\HttpFoundation\Request;
class HomeController extends CoralBaseController
{
public function indexAction(Request $request)
{
var_dump($request->request->all());
die;
return $this->render('CoralWalletBundle:Home:index.html.twig');
}
}
use $request->request->all() to get all POST parameters in an array. To get only a specific parameter you can use $request->request->get('my_param'). If you ever need to acces GET parameters you can use $request->query->get('my_param'), but better set query parameters already in the routing pattern.
I think you're trying to do this:
$client = static::createClient();
$client->request($method, $url, [], [], [], json_encode($content));
$this->assertEquals(
200,
$client->getResponse()
->getStatusCode()
);
You're putting your data (content) in as the params array but you want to put it in as the raw body content which is a JSON encoded string.