Guzzle 6: no more json() method for responses - php

Previously in Guzzle 5.3:
$response = $client->get('http://httpbin.org/get');
$array = $response->json(); // Yoohoo
var_dump($array[0]['origin']);
I could easily get a PHP array from a JSON response. Now In Guzzle 6, I don't know how to do. There seems to be no json() method anymore. I (quickly) read the doc from the latest version and don't found anything about JSON responses. I think I missed something, maybe there is a new concept that I don't understand (or maybe I did not read correctly).
Is this (below) new way the only way?
$response = $client->get('http://httpbin.org/get');
$array = json_decode($response->getBody()->getContents(), true); // :'(
var_dump($array[0]['origin']);
Or is there an helper or something like that?

I use json_decode($response->getBody()) now instead of $response->json().
I suspect this might be a casualty of PSR-7 compliance.

You switch to:
json_decode($response->getBody(), true)
Instead of the other comment if you want it to work exactly as before in order to get arrays instead of objects.

I use $response->getBody()->getContents() to get JSON from response.
Guzzle version 6.3.0.

If you guys still interested, here is my workaround based on Guzzle middleware feature:
Create JsonAwaraResponse that will decode JSON response by Content-Type HTTP header, if not - it will act as standard Guzzle Response:
<?php
namespace GuzzleHttp\Psr7;
class JsonAwareResponse extends Response
{
/**
* Cache for performance
* #var array
*/
private $json;
public function getBody()
{
if ($this->json) {
return $this->json;
}
// get parent Body stream
$body = parent::getBody();
// if JSON HTTP header detected - then decode
if (false !== strpos($this->getHeaderLine('Content-Type'), 'application/json')) {
return $this->json = \json_decode($body, true);
}
return $body;
}
}
Create Middleware which going to replace Guzzle PSR-7 responses with above Response implementation:
<?php
$client = new \GuzzleHttp\Client();
/** #var HandlerStack $handler */
$handler = $client->getConfig('handler');
$handler->push(\GuzzleHttp\Middleware::mapResponse(function (\Psr\Http\Message\ResponseInterface $response) {
return new \GuzzleHttp\Psr7\JsonAwareResponse(
$response->getStatusCode(),
$response->getHeaders(),
$response->getBody(),
$response->getProtocolVersion(),
$response->getReasonPhrase()
);
}), 'json_decode_middleware');
After this to retrieve JSON as PHP native array use Guzzle as always:
$jsonArray = $client->get('http://httpbin.org/headers')->getBody();
Tested with guzzlehttp/guzzle 6.3.3

$response is instance of PSR-7 ResponseInterface. For more details see https://www.php-fig.org/psr/psr-7/#3-interfaces
getBody() returns StreamInterface:
/**
* Gets the body of the message.
*
* #return StreamInterface Returns the body as a stream.
*/
public function getBody();
StreamInterface implements __toString() which does
Reads all data from the stream into a string, from the beginning to end.
Therefore, to read body as string, you have to cast it to string:
$stringBody = $response->getBody()->__toString()
Gotchas
json_decode($response->getBody() is not the best solution as it magically casts stream into string for you. json_decode() requires string as 1st argument.
Don't use $response->getBody()->getContents() unless you know what you're doing. If you read documentation for getContents(), it says: Returns the remaining contents in a string. Therefore, calling getContents() reads the rest of the stream and calling it again returns nothing because stream is already at the end. You'd have to rewind the stream between those calls.

Adding ->getContents() doesn't return jSON response, instead it returns as text.
You can simply use json_decode

Related

How can i set multiple Header Keys with Slim Framework 3

I am using the below sample code to return data to the browser
$response->write($image);
return $response->withHeader('Content-Type', 'image/jpeg');
which works fine but how would i go about it if i wanted to also return Content-Length ?
the only option i found so far is to copy the response into a new object like below but i don't think that's efficient if i have a $image in the response.
$response = $response->withAddedHeader('Content-Length', strlen($image));
I tried it as array but that doesn't work..
Quoting from Slim 3 docs
Reminder
Unlike the withHeader() method, this method appends the new value to the set of values that already exist for the same header name. The Response object is immutable. This method returns a copy of the Response object that has the appended header value.
Both withHeader() and with appendedHeader() methods return a copy of the response object. So even if your do not assign the return value of $response->withHeader() to a variable and return the result directly, you are still working with a copy of the response object.
Regarding your concern about efficiency, you should use streams instead of string as response body. The following is an example of how to use streams for returning an image as the response:
<?php
use Slim\App;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Http\Body;
return function (App $app) {
$container = $app->getContainer();
$app->get('/', function(Request $request, Response $response) {
$image = fopen('sample.jpg', 'r');
return $response->withBody(new Body($image))
->withHeader('Content-Type', 'image/jpeg')
->withAddedHeader('Content-Length', fstat($image)['size']);
});
};

How to avoid serializer to escape double quote to \u0022?

In my controller I want to return a JsonResponse:
public function index(CkeditorTemplateRepository $ckeditorTemplateRepository, SerializerInterface $serializer): Response
{
$data = $ckeditorTemplateRepository->findAll();
return new JsonResponse($serializer->serialize($data, 'json'));
}
But when I request this endpoint, the response includes too many \u prefixes:
"[{\u0022created\u0022:\u00222019-08-31T07:28:56+00:00\u0022,\u0022id\u0022:1,\u0022content\u0022:\u0022\u003Ctr\u003E\u003Ctd height=\\u002252\\u0022 valign=\\u0022bottom\\u0022 class=\\u0022h24\\u0022\u003E\u003Cspan class=\\u0022h21copy1\\u0022\u003E\u5b66\u4e60\u56de\u526f\u603b\u7406\u7684\u8bb2\u8bdd \u003C\/span\u003E\u003C\/td\u003E\u003C\/tr\u003E\u003Ctr\u003E\u003Ctd valign=\\u0022top\\u0022\u003E\u003Cp style=\\u0022margin-bottom:10\\u0022 align=\\u0022center\\u0022 class=\\u0022font6\\u0022\u003E\u003C\/p\u003E\u003C\/td\u003E\u003C\/tr\u003E\u0022}]"
The problem is that you are doing it wrong.
JsonResponse is a response object that helps you with serializing the response data to JSON.
But you are already doing the seralization yourself, so it is a bit redundant.
Couple of options:
return (new Response($serializer->serialize($data, 'json'))
->headers->set('Content-type', 'application/json');
Or if you keep using JsonResponse:
return (new JsonResponse())->setContent($serializer->serialize($data, 'json'));
Or instantiate JsonResponse directly from a factory method:
return JsonResponse::fromJsonString($serializer->serialize($data, 'json'));
(in either case, no need to set the content type, since JsonResponse does that for you.)
If you do:
new JsonResponse($data);
what you get is a response where the content is the JSON serialiazed $data. This works for simple data structures which you can easily serialize by calling json_encode. But you were already sending a JSON string, so by doing it this way you were serializing the data twice.
return new JsonReponse($data) deals with arrays, strings etc. It will json_encode
that data for output. With a pre-serialised string, you can use return JsonResponse::fromJsonString('{"key": "value"}'). Both accept a $status and an array of headers.
JsonResponse::fromJsonString() is, in fact, just a shortcut for calling the constructor with its final parameter with true.
__construct($data = null, int $status = 200, array $headers = [], bool $json = false)

How do I "extract" the data from a ReactPHP\Promise\Promise?

Disclaimer: This is my first time working with ReactPHP and "php promises", so the solution might just be staring me in the face 🙄
I'm currently working on a little project where I need to create a Slack bot. I decided to use the Botman package and utilize it's Slack RTM driver. This driver utilizes ReactPHP's promises to communicate with the RTM API.
My problem:
When I make the bot reply on a command, I want to get the get retrieve the response from RTM API, so I can cache the ID of the posted message.
Problem is that, the response is being returned inside one of these ReactPHP\Promise\Promise but I simply can't figure out how to retrieve the data.
What I'm doing:
So when a command is triggered, the bot sends a reply Slack:
$response = $bot->reply($response)->then(function (Payload $item) {
return $this->reply = $item;
});
But then $response consists of an (empty?) ReactPHP\Promise\Promise:
React\Promise\Promise^ {#887
-canceller: null
-result: null
-handlers: []
-progressHandlers: & []
-requiredCancelRequests: 0
-cancelRequests: 0
}
I've also tried using done() instead of then(), which is what (as far as I can understand) the official ReactPHP docs suggest you should use to retrieve data from a promise:
$response = $bot->reply($response)->done(function (Payload $item) {
return $this->reply = $item;
});
But then $response returns as null.
The funny thing is, during debugging, I tried to do a var_dump($item) inside the then() but had forgot to remove a non-existing method on the promise. But then the var_dump actually returned the data 🤯
$response = $bot->reply($response)->then(function (Payload $item) {
var_dump($item);
return $this->reply = $item;
})->resolve();
So from what I can fathom, it's like I somehow need to "execute" the promise again, even though it has been resolved before being returned.
Inside the Bot's reply method, this is what's going on and how the ReactPHP promise is being generated:
public function apiCall($method, array $args = [], $multipart = false, $callDeferred = true)
{
// create the request url
$requestUrl = self::BASE_URL . $method;
// set the api token
$args['token'] = $this->token;
// send a post request with all arguments
$requestType = $multipart ? 'multipart' : 'form_params';
$requestData = $multipart ? $this->convertToMultipartArray($args) : $args;
$promise = $this->httpClient->postAsync($requestUrl, [
$requestType => $requestData,
]);
// Add requests to the event loop to be handled at a later date.
if ($callDeferred) {
$this->loop->futureTick(function () use ($promise) {
$promise->wait();
});
} else {
$promise->wait();
}
// When the response has arrived, parse it and resolve. Note that our
// promises aren't pretty; Guzzle promises are not compatible with React
// promises, so the only Guzzle promises ever used die in here and it is
// React from here on out.
$deferred = new Deferred();
$promise->then(function (ResponseInterface $response) use ($deferred) {
// get the response as a json object
$payload = Payload::fromJson((string) $response->getBody());
// check if there was an error
if (isset($payload['ok']) && $payload['ok'] === true) {
$deferred->resolve($payload);
} else {
// make a nice-looking error message and throw an exception
$niceMessage = ucfirst(str_replace('_', ' ', $payload['error']));
$deferred->reject(new ApiException($niceMessage));
}
});
return $deferred->promise();
}
You can see the full source of it here.
Please just point me in some kind of direction. I feel like I tried everything, but obviously I'm missing something or doing something wrong.
ReactPHP core team member here. There are a few options and things going on here.
First off then will never return the value from a promise, it will return a new promise so you can create a promise chain. As a result of that you do a new async operation in each then that takes in the result from the previous one.
Secondly done never returns result value and works pretty much like then but will throw any uncaught exceptions from the previous promise in the chain.
The thing with both then and done is that they are your resolution methods. A promise a merely represents the result of an operation that isn't done yet. It will call the callable you hand to then/done once the operation is ready and resolves the promise. So ultimately all your operations happen inside a callable one way or the other and in the broadest sense. (Which can also be a __invoke method on a class depending on how you set it up. And also why I'm so excited about short closures coming in PHP 7.4.)
You have two options here:
Run all your operations inside callable's
Use RecoilPHP
The former means a lot more mind mapping and learning how async works and how to wrap your mind around that. The latter makes it easier but requires you to run each path in a coroutine (callable with some cool magic).

How to get webhook response data

I'm still new to webhook. What I need to do here is to do a callback whenever there's a new registration on the registration platform called Bizzabo. This platform has provided Webhook integration by having us putting the Endpoint URL and select which action that will trigger the Webhook. I've also used Request Bin and it displays the data well.
However, how can I echo the JSON body data like how it displayed in Request Bin in my interface URL php?
This is how the Webhook integration looks like on Bizzabo
Data captured from Webhook when tested using Request Bin
Thank you!
Your need an endpoint which receives the callback instead Request Bin, then access it in the following way using file_get_contents('php://input') and json_decode()
For example http://example.com/bizzabo-callback-handler.php
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// fetch RAW input
$json = file_get_contents('php://input');
// decode json
$object = json_decode($json);
// expecting valid json
if (json_last_error() !== JSON_ERROR_NONE) {
die(header('HTTP/1.0 415 Unsupported Media Type'));
}
/**
* Do something with object, structure will be like:
* $object->accountId
* $object->details->items[0]['contactName']
*/
// dump to file so you can see
file_put_contents('callback.test.txt', print_r($object, true));
}
If the data receiving in compressed format(gzip) use gzdecode :
<?php
if (!function_exists('gzdecode')){
function gzdecode($data){
// strip header and footer and inflate
return gzinflate(substr($data, 10, -8));
}
}
// get compressed (gzip) POST request into a string
$comprReq = file_get_contents('php://input');
// get decompressed POST request
$decomprReq = gzdecode($comprReq);
// decode to json
$jsonData = json_decode($decomprReq, true);
// do your processing on $jsonData
?>

Fosrestbundle body empty when multipart request

In the code bellow I expect the $request->getContents() to get the body content of the HTTP request. When sending non multipart request this works as expected though when using multipart requests the $body variable remains empty.
public function postDebugAction(Request $request) {
$body = $request->getContent();
if (empty($body)) {
throw new \Exception('Body empty.');
}
return $this->view(array(), 201);
}
After reading this question and answer I added a body listener aswell.
<?php
namespace VSmart\ApiBundle\Listener;
use FOS\RestBundle\EventListener\BodyListener as BaseBodyListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use FOS\RestBundle\Decoder\DecoderProviderInterface;
class BodyListener extends BaseBodyListener {
/**
* #var DecoderProviderInterface
*/
private $decoderProvider;
/**
* #param DecoderProviderInterface $decoderProvider Provider for fetching decoders
*/
public function __construct(DecoderProviderInterface $decoderProvider) {
$this->decoderProvider = $decoderProvider;
}
/**
* {#inheritdoc}
*/
public function onKernelRequest(GetResponseEvent $event) {
$request = $event->getRequest();
if (strpos($request->headers->get('Content-Type'), 'multipart/form-data') !== 0) {
return;
}
$format = 'json';
if (!$this->decoderProvider->supports($format)) {
return;
}
$decoder = $this->decoderProvider->getDecoder($format);
$iterator = $request->request->getIterator();
$request->request->set($iterator->key(), $decoder->decode($iterator->current(), $format));
}
}
According to my PHPUnit test this was working though when using Postman and Advanced Rest Client to simulate the request the body seems to be empty again. I double checked this to run both the simulate requests as PHPUnit with the debugger. Result is that, indeed, the body is empty when simulated via a Rest client and not empty when ran through PHPUnit.
The test case I used:
POST url:
http://localhost/EntisServer/web/app_dev.php/api2/debug
Headers:
Authorization: Bearer ZGYzYjY1YzY4MGY3YWM3OTFhYTI4Njk3ZmI0NmNmOWZmMjg5MDFkYzJmOWZkOWE4ZTkyYTRmMGM4NTE1MWM0Nw
Content-Type: multipart/form-data; boundary=-----XXXXX
Content:
-----XXXXX
Content-Disposition: form-data; name="json"
Content-Type: application/json; charset=utf-8
{
"blabla": 11
}
-----XXXXX
Content-Disposition: form-data; name="q_3101"; filename="image.jpg"
Content-Type: image/jpeg
contents of a file...
-----XXXXX--
UPDATE
I was uncertain whether I stepped through the debugger without using the BodyListener. When I did the result is exactly the same. So, without the BodyListener the PHPUnit case gets the body though the simulated request is still empty.
See php:// wrappers on php.net:
Note: Prior to PHP 5.6, a stream opened with php://input could only be read once; the stream did not support seek operations. However, depending on the SAPI implementation, it may be possible to open another php://input stream and restart reading. This is only possible if the request body data has been saved. Typically, this is the case for POST requests, but not other request methods, such as PUT or PROPFIND.
So update your PHP version or make sure you only read the input once.
You can find your uploaded files in $request->files->all() after fos_rest.decoder_provider decoding.

Categories