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']);
});
};
Related
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)
This May sound like a silly question but, I have a method that normally returns a JSON response to the client side. But now I need the same method within the class to prevent DRY. Something like:
public function deleteChild($id){
// delete the element with given id ...
if($success){
return response()->json(['success'=>'successfully deleted'], 200);
}else{
return response()->json(['error'=>'could not be deleted'], 422);
}
}
This method is already used by the client side. But now I have another one that needs this method as well. Something like:
public function deleteMaster($id){
$master = Master::find($id);
foreach($child as $master->children){
$child_json_response = $this->deleteChild($child->id);
$response_data = $child_json_response->getData();
if($response_data->error){
// handle child error response
// ...
}
// delete master itself
}
}
Here I can extract the response data with getData() and process it.
Is this the right way to use a sibling function that returns a JSON response (actually made for client-side) or should I create another method that returns direct results serving server-side only?
You should use a Response macro
Into a service provider's boot, add this:
\Illuminate\Http\Response::macro('deleteJson', function ($success) {
return $success
? $this->json(['success'=>'successfully deleted'], 200)
: $this->json(['error'=>'could not be deleted'], 422);
}
public function deleteChild($id){
// delete element with given id ...
return response()->deleteJson($success);
}
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).
I'm using Slim Framework version 3.
I've followed the tutorial on creating an app. Part of this involves setting up a classes directory where you can put your own PHP classes.
What I can't understand is how you can access Slim inside those. For example if I have a class src/classes/Users.php and wanted to use the Slim Response code e.g.
return $response->withStatus(302)->withHeader('Location', 'login');
Obviously, $response, is not accessible at that point. It only seems to be in index.php where each callback recieves it as an argument.
Do I have to pass something to every constructor of my own classes, or use or require statements in my classes?
I'd say when domain layer components need to access application level components - this is a code smell. So, consider doing things otherwise, request object describes request. Request contains some data, and that data should be passed to your User class, not request object itself.
If you still wish to use Request object in Users class, simply pass it as argument, like this:
// in your routes
$app->post('users', function($request, $response) {
$user = new User;
$user->hydrateAndPersist($request); // there you pass it as argument
});
// in your user class
class User
{
public function hydrateAndPersist(
\Psr\Http\Message\ServerRequestInterface $request,
\Psr\Http\Message\ResponseInterface $response // yes, you force yourself into injecting response object
) {
// request is here, let's extract data from it
$submittedData = $request->getParams();
// terrible indeed, but this is just an example:
foreach ($submittedData as $prop => $value) {
$this->prop = $value;
}
$result = $this->save();
return $response->withJson($result);
}
}
However, in this case your User class is coupled heavily with PSR-7 request and response objects. Sometimes coupling is not a problem, but in your case User class belongs to domain layer (since it describes User entity), while $request and $response are components of application layer.
Try to reduce coupling:
$app->post('users', function($request, $response) {
$submittedData = $request->getParams();
$user = new User;
$result = $user->hydrateAndPersist($submittedData);
// response is already declared in this scope, let's "put" result of the operation into it
$response = $response->withJson($result);
return $response;
});
class User
{
public function hydrateAndPersist(array $data) : bool
{
$result = false;
foreach ($submittedData as $prop => $value) {
$this->prop = $value;
}
$result = $this->save();
return $result;
}
}
See the benefit? User::hydrateAndPersist now accepts array as argument, it has no knowledge of $request and $response. So, it is not tied to HTTP (PSR-7 describes HTTP messages), it can work with anything. Classes separated, layers separated, ease of maintenance.
To sum up: you can access $request object in your User class by simply passing $request to one of User methods. However, this is poor design that will reduce maintainability of your code.
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