Slim 3 post method returns empty - php

When I submit a from action to this url, and also test this api via Postman. It is not printing POST data.
But get method is working.
$app = new \Slim\App;
$dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load();
$app->add(new \Slim\Middleware\JwtAuthentication([
//"secure" => false,
//"relaxed" => ["localhost", "api.f2f.dev"],
"header" => "X-Token",
"path" => ["/v2"],
"passthrough" => ["/v1/api/token/", "/test", "/v1"],
"secret" => getenv("TOKEN_SECRET")
]));
$app->post("/v1/app/register", function ($request, $response, $arguments) {
return $allPostPutVars = $request->getParsedBody();
});
I couldn't find the issue i this. But unparsed data is able to print.
Any help is welcome. Any post method on Slim 3 is also welcomed.
Thank you.

Your callback should return response object that implements Psr\Http\Message\ResponseInterface. It doesn't. So, as an example:
$app->post("/v1/app/register", function ($request, $response, $arguments) {
$params = $request->getParams();
return $response->getBody()->write('You have posted '.count($params).' parameters.');
});
Sometimes you need a quick and dirty check. Then you can do the following:
$app->post("/v1/app/register", function ($request, $response, $arguments) {
$params = $request->getParams();
print_r($params);
die();
});

Related

Slim3 Cannot use object of type Slim\Http\Response as array

I am having trouble with adding error output to the JWT middleware set up.
I am getting this error: Cannot use object of type Slim\Http\Response as array
I am using Slim 3 and the slim-jwt-auth package, I am using the sample code in the docs found at https://github.com/tuupola/slim-jwt-auth#error
The difference being I'm calling \Slim\Middleware\JwtAuthentication instead of Tuupola\Middleware\JwtAuthentication. If I use that the class cannot be found. Everything was working fine until I wanted to add the error output to the middleware set up, here is my code:
$app->add(new \Slim\Middleware\JwtAuthentication([
"path" => "/mypath",
"passthrough" => "/mypath/get-auth",
"secret" => getenv("SKEY"),
"secure" => false,
"error" => function ($response, $args) {
$data = array();
$data["status"] = "error";
$data["message"] = $args["message"];
return $response
->withHeader("Content-Type", "application/json")
->getBody()->write(
json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
]));
The error output says it's coming from the line $data["message"] = $args["message"];.
Am I looking right at the problem and not seeing it?
The function signature for the "error" closure is:
function ($request, $response, $args): Response
You're missing the first parameter in your code, so when you use $args, you're getting the Response object.

PHP - Guzzle Middleware

I'm using the Pole Emploi's API,but I encounter 401 error 25 minutes later, when my token expires.
I looked for a way to get a new token and retry the request, but no way for me to understand how Middlewares work, and if I should use a middleware for my needings.
On Guzzle's docs this is written :
Middleware functions return a function that accepts the next handler to invoke. This returned function then returns another function that acts as a composed handler-- it accepts a request and options, and returns a promise that is fulfilled with a response. Your composed middleware can modify the request, add custom request options, and modify the promise returned by the downstream handler.
And this is an example code from the docs :
use Psr\Http\Message\RequestInterface;
function my_middleware()
{
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
return $handler($request, $options);
};
};
}
So I think I need to manage the "promise" to see if its HTTP code is 401, and then get a new token and retry the request ?
I'm lost, so I would appreciate if someone can explain me the logic of this with different words maybe :)
Thank you in advance.
It doesn't need to be that difficult, add a handler that takes care of the job, in combination with cache that expires.
If you don't use cache then I guess you could probably save it to a file along with a timestamp for expiration that you check against when fetching it.
class AuthenticationHandler
{
private $username;
private $password;
private $token_name = 'access_token';
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
public function __invoke(callable $handler)
{
return function (RequestInterface $request, array $options) use ($handler) {
if (is_null($token = Cache::get($this->token_name))) {
$response = $this->getJWT();
Cache::put($this->token_name, $token = $response->access_token, floor($response->expires_in));
}
return $handler(
$request->withAddedHeader('Authorization', 'Bearer '.$token)
->withAddedHeader('Api-Key', $this->api_key), $options
);
};
}
private function getJWT()
{
$response = (new Client)->request('POST', 'new/token/url', [
'form_params' => [
'grant_type' => 'client_credentials',
'username' => $this->username,
'password' => $this->password,
],
]);
return json_decode($response->getBody());
}
}
Then use it:
$stack = HandlerStack::create(new CurlHandler());
$stack->push(new AuthenticationHandler('username', 'password'));
$client = new GuzzleHttp\Client([
'base_uri' => 'https://api.com',
'handler' => $stack,
]);
Now you will always have a valid token, and you will never have to worry about it ever again.
I wouldn't recommend doing this as it can become hell to debug your application and as far as I am aware Guzzle doesn't really allow access to the client from middleware. Regardless you can use Promises to get around. If I were you I would refresh token before other requests, or refresh periodically. It might be fine if you are firing requests one by one, but in a Pool it will become a nightmare because you can end up having script fetch token too often and then some request ends up with out-dated token.
Anyway here is a rough example:
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
function my_middleware()
{
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
/**
* #var $promise \GuzzleHttp\Promise\Promise
*/
$promise = $handler($request, $options);
return $promise->then(
function (ResponseInterface $response) use ($request, $options) {
if ($response->getStatusCode() === 404) {
var_dump($response->getStatusCode());
var_dump(strlen($response->getBody()));
// Pretend we are getting new token key here
$client = new Client();
$key = $client->get('https://www.iana.org/domains/reserved');
// Then we modify the failed request. For your case you use ->withHeader() to change the
// Authorization header with your token.
$uri = $request->getUri();
$uri = $uri->withHost('google.com')->withPath('/');
// New instance of Request
$request = $request->withUri($uri);
// Send the request again with our new header/URL/whatever
return $client->sendAsync($request, $options);
}
return $response;
}
);
};
};
}
$handlerStack = HandlerStack::create();
$handlerStack->push(my_middleware());
$client = new Client([
'base_uri' => 'https://example.org',
'http_errors' => false,
'handler' => $handlerStack
]);
$options = [];
$response = $client->request('GET', '/test', $options);
var_dump($response->getStatusCode());
var_dump(strlen($response->getBody()));
echo $response->getBody();

PHPUnit and mock request from Guzzle

I have a class with the following function :
public function get(string $uri) : stdClass
{
$this->client = new Client;
$response = $this->client->request(
'GET',
$uri,
$this->headers
);
return json_decode($response->getBody());
}
How can I mock the request method from PHPUnit? I tried different ways but it always tries to connect to the uri specified.
I tried with :
$clientMock = $this->getMockBuilder('GuzzleHttp\Client')
->setMethods('request')
->getMock();
$clientMock->expects($this->once())
->method('request')
->willReturn('{}');
But this didn't work. What can I do? I just need to mock the response to be empty.
Thanks
PD : Client comes from (use GuzzleHttp\Client)
I think as suggested is better to use http://docs.guzzlephp.org/en/stable/testing.html#mock-handler
as it looks like the most elegant way to do it properly.
Thank you all
The mocked Response doesn't need to be anything in particular, your code just expects it to be an object with a getBody method. So you can just use a stdClass, with a getBody method which returns some json_encoded object. Something like:
$jsonObject = json_encode(['foo']);
$uri = 'path/to/foo/bar/';
$mockResponse = $this->getMockBuilder(\stdClass::class)->getMock();
$mockResponse->method('getBody')->willReturn($jsonObject);
$clientMock = $this->getMockBuilder('GuzzleHttp\Client')->getMock();
$clientMock->expects($this->once())
->method('request')
->with(
'GET',
$uri,
$this->anything()
)
->willReturn($mockResponse);
$result = $yourClass->get($uri);
$expected = json_decode($jsonObject);
$this->assertSame($expected, $result);
I prefer this way to mock a Client in PHP. In this example I am using Guzzle Client.
Clone the code or install it via composer
$ composer require doppiogancio/mocked-client
And then...
$builder = new HandlerStackBuilder();
// Add a route with a response via callback
$builder->addRoute(
'GET', '/country/IT', static function (ServerRequestInterface $request): Response {
return new Response(200, [], '{"id":"+39","code":"IT","name":"Italy"}');
}
);
// Add a route with a response in a text file
$builder->addRouteWithFile('GET', '/country/IT/json', __DIR__ . '/fixtures/country.json');
// Add a route with a response in a string
$builder->addRouteWithFile('GET', '{"id":"+39","code":"IT","name":"Italy"}');
// Add a route mocking directly the response
$builder->addRouteWithResponse('GET', '/admin/dashboard', new Response(401));
$client = new Client(['handler' => $builder->build()]);
Once you have mocked the client you can use it like this:
$response = $client->request('GET', '/country/DE/json');
$body = (string) $response->getBody();
$country = json_decode($body, true);
print_r($country);
// will return
Array
(
[id] => +49
[code] => DE
[name] => Germany
)
In addition to the current answer about using MockHandler, it's possible to process the request so that you can validate the calls.
The following example passes a callable which just tests the request method and throws an exception if not POST, if that is OK it returns the response. The principle can be expanded to test other details about the request...
$mock = new MockHandler([
function ($request) {
$this->assertEquals('POST', $request->getMethod());
return new Response(
200,
[],
json_encode([ "access_token" => '1234e' ])
);
},
new Response(
200,
[],
json_encode([ "details" =>
[
[
"orderID" => 229783,
],
[
"orderID" => 416270,
],
],
])
),
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
So the first call to the client has the test included, the second call just returns a response.
Just noticed that any time you use a callable to process the request, you MUST return a Response object if you expect the process to continue.

ReactPHP and Promises

I'm trying to understand the concept of Promises using ReactPHP
$app = function ($request, $response) use ($redis, $config) {
$promise = React\Promise\all(
array(
AsyncGetUser(),
AsyncGetDB(),
AsyncGetTemplate()
)
)->then(function ($res) {
$result = ParseTemplate($user, $template, $whatever);
}
\React\Promise\resolve($promise);
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end($result);
}
$http->on('request', $app);
But $response is sent before $result is ready.
How can do something like await for $promise so I send $result properly ?
I've tried to move $response->end to another->then() section but then I don't get any response in browser (i.e. the script gets a result when $app = function is finished already).
I don't know reactphp at all, but if promises work like promises in JS for example, seems like you need to write the response in the ->then where you have a result!
$app = function ($request, $response) use ($redis, $config) {
$promise = React\Promise\all(
array(
AsyncGetUser(),
AsyncGetDB(),
AsyncGetTemplate()
)
)->then(function ($res) {
$result = ParseTemplate($user, $template, $whatever);
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end($result);
}
}
$http->on('request', $app);
Note: the following line in your code
\React\Promise\resolve($promise);
makes no sense. \React\Promise\resolve doesn't "resolve the promise" as you seem to think, it creates and returns a resolved promise - which you discard!

How to get authentication with jwt slim middleware?

This code works, but how can I get the permissions to see the /api content with a get request??
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require 'vendor/autoload.php';
$app = new \Slim\App();
$app->add(new \Slim\Middleware\JwtAuthentication([
"path" => "/api",
"secret" => "1234"
]));
$app->get('/api', function (Request $request, Response $response) {
echo "Hi";
});
$app->get('/teste', function (Request $request, Response $response) {
echo "Hi";
});
$app->run();
1. Generate Token
Using firebase/php-jwt
$payload = [
"sub" => "user#example.com"
];
$token = JWT::encode($payload,'JWT-secret-key');
2. .htaccess Changes
If using Apache add the following to the .htaccess file. Otherwise PHP wont have access to Authorization: Bearer header
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
3. Middleware
$app->add(new \Slim\Middleware\JwtAuthentication([
"path" => "/api",
"passthrough" => ["/teste"],
"secret" => "JWT-secret-key",
"secure" => false,
"callback" => function ($request, $response, $arguments) use ($container) {
$container["jwt"] = $arguments["decoded"];
},
"error" => function ($request, $response, $arguments) {
$data["status"] = "0";
$data["message"] = $arguments["message"];
$data["data"] = "";
return $response
->withHeader("Content-Type", "application/json")
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
]));
4. Correct Request
5. Wrong Token Request
Reference Link
i used Authorization: Bearer Mykey , the key need to be encode in jwt mode

Categories