I have the following PHP / PHPUnit code:
<?php
namespace Experiments\Tests;
use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\RequestOptions;
use PHPUnit\Framework\TestCase;
final class ApiGatewayTest extends TestCase
{
public function testExecute()
{
$req = json_decode('{ "root": { "param1": [ "value1" ] } }', true);
$resp = '{ "echo": { "root": { "param1": [ "value1" ] } } }';
$options = [
RequestOptions::HEADERS => [
'Content-Type' => 'application/json',
],
RequestOptions::JSON => $req,
RequestOptions::TIMEOUT => 10,
];
$handlerStack = HandlerStack::create(
new MockHandler([new Response(200, ['Content-Type' => 'application/json'], $resp)])
);
$client = new HttpClient(['handler' => $handlerStack]);
$response = $client->request(
'POST',
'whatever-just-mocking',
$options
);
$responseBodyJson = (string) $response->getBody();
$responseBody = json_decode($responseBodyJson, true);
$this->assertSame('value1', $responseBody['echo']['root']['param1'][0]);
}
}
The unit test above works just fine. You can test it with:
$ ./vendor/bin/phpunit --filter 'testExecute\b'
What I need: Delay the mock response for 5 seconds, so I can implement and test a timeout handling in the same class above.
Any idea on how to do that?
Thanks!
Related
I want to consume a Rest API in Laravel (an MVC framework) but I resort to use __call and was wonder if there is a better design pattern for this.
I know this a bad choice and I'm looking for an alternative pattern but here is my Repository class:
namespace App\Repositories;
use App\Models\OnlinePayment;
use App\Models\Order;
use App\Models\Transaction;
use App\Models\User;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use SoapClient;
class Bank
{
protected $http;
protected $user;
public function __construct()
{
$this->http = new Client;
}
protected function index()
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/report';
$data = [
'user_gender' => $this->user->gender ?? 1,
'user_name' => $this->user->name,
'user_family' => $this->user->family ?? 'خالی',
'user_mobile' => $this->user->mobile,
'user_type' => $this->user->type->name,
];
$options = $this->options($data);
$res = $this->http->request('GET', $url, $options);
$response = json_decode($res->getBody(), true);
return $response;
}
protected function indexData($request)
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers';
$options = $this->options($request->all());
$res = $this->http->request('GET', $url, $options);
$response = response()->json(json_decode($res->getBody(), true), $res->getStatusCode());
return $response;
}
protected function show($national_id)
{
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers/' . $national_id;
$options = $this->options([]);
$res = $this->http->request('GET', $url, $options);
if ($res->getStatusCode() == 404) {
abort(404);
}
$response = json_decode($res->getBody(), true);
return $response;
}
protected function store($request)
{
$http = new Client;
$url = config('Bank.url') . '/v2/quantum/users/' . $this->user->national_id . '/customers';
$this->user = auth()->user();
$data = array_merge(
[
'customer_national_id' => $request->national_id,
'customer_gender' => $request->gender,
'customer_name' => $request->name,
'customer_family' => $request->family,
'customer_phone' => $request->phone,
'customer_mobile' => $request->mobile,
'customer_city_id' => $request->city_id,
], [
'user_name' => $this->user->nanfig() is a hidden dependency. The settings should also be passed via the construcme,
'user_family' => $this->user->family ?? 'خالی',
'user_mobile' => $this->user->mobile,
'user_type' => $this->user->type->name,
'user_gender' => $this->user->gender ?? 1,
]
);
$res = $http->request('POST', $url, [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . config('Bank.token'),
],
'json' => $data,
'http_errors' => false
]);
if (! in_array($res->getStatusCode(), [200, 422])) {
$error = ValidationException::withMessages([
'name' => 'خطای ' . $res->getStatusCode() . ' در تعویض کالا'
]);
throw $error;
}
$response = response()->json(json_decode($res->getBody(), true), $res->getStatusCode());
return $response;
}
protected function options($data)
{
$options = [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . config('Bank.token'),
],
'json' => $data,
'http_errors' => false
];
return $options;
}
public function __call($method, $arguments) {
if (method_exists($this, $method)) {
if (! isset($arguments[0]) || ! $arguments[0] instanceof User) {
$this->user = auth()->user();
} else {
$this->user = $arguments[0];
unset($arguments[0]);
}
return call_user_func_array(array($this, $method), $arguments);
}
}
}
then create an instance of it in controller constructor:
public function __construct()
{
$this->Bank = new Bank();
}
and use it in controller like this:
$response = $this->Bank->indexData($user, $request);
or this:
$response = $this->Bank->indexData($request);
I think the shown class is not a Repository class because a Repository is only responsible for reading and writing the date from a data source. Your class does too much and violates all basic MVC principles.
Some thinks I would fix:
A repository is not responsible for creating the response view data (like JSON)
A repository is not responsible for creating a response object
A repository is independent of the request/response
The method name index makes no sense because a repository is not a Controller action. Don't mix the model layer with the controller layer.
config() is a hidden dependency. The settings should also be passed via the constructor.
Instead use better separation:
Create a class BankApiClient
Dont use magic methods like __call
Instead use public methods like: getUserByNationalId(int $nationalId): UserData
and so on...
Let the controller action create / render the json response with the results of the BankApiClient.
__call is an magic method of php which allow to execute protected method outside of the object instance, this is a rupture of the class visibility.
If you want to call a method from outside it must be public
public function __construct()
{
$this->bank = new Bank()
}
Use Auto injection of the dependency
public function __construct(Bank $bank)
{
$this->bank = $bank;
}
I have:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use GuzzleHttp\Client;
class GetBlahCommand extends Command {
protected $name = 'blah:blah';
protected $description = "name";
public function handle()
{
$client = new Client();
$res = $client->request('GET', 'https://someapi.com', [
'api_key' => ['privatekey']
]);
echo $res->getStatusCode();
}
}
But the param api_key isn't being passed along.
How can I get this to work?
I have adjusted my code, but now I am getting NULL returned:
$ndbnos = [
'ndbno' => '01009'
];
$client = new Client(['base_uri' => 'https://soemapi.com']);
$res = $client->request('GET', '/', [
'query' => array_merge([
'api_key' => 'somekey'
], $ndbnos)
])->getBody();
$res = json_decode($res);
var_dump($res);
I figured it out:
public function handle()
{
$ndbnos = [
'ndbno' => '01009'
];
$client = new Client(['base_uri' => 'https://someapi.com']);
$res = $client->request('GET', '', [
'query' => array_merge([
'api_key' => 'somekey',
'format' => 'json'
], $ndbnos)
]);
print_r(json_decode($res->getBody()));
}
You can be do it by the following way:
public function handle()
{
$client = new Client(['base_uri' => 'https://someapi.com/']);
$res = $client->request('GET', '/', [
'headers' => [
'api_key' => 'YOUR_KEY'
]
]);
}
I thought it's a header parameter. If it's a form input you can do it by the following way:
public function handle()
{
$client = new Client(['base_uri' => 'https://someapi.com/']);
$res = $client->request('GET', '/', [
'query' => [
'api_key' => 'YOUR_KEY'
]
]);
}
i use lumen 5.5.2 and i wanna create http test for that.so i used this code:
class ExampleTest extends \TestCase
{
public function testExample()
{
$header = [
'access-token' => '$2y$10$Z6PvDMkTVVaSQJD10YGEsOz6E2KdQ2Q9RKo7roanZIAwRz0thUoAS',
'refresh-token' => '$2y$10$WA12Phc979GKN9xht89Fv.RSeaS/R4MErWTjLYY1mNC8vS03E84XC',
'app-name' => 'test',
'app-secret' => 'test',
'Content-Type' => 'application/json'
];
$json=[
"gdata"=>[
"title"=>"testTitle",
"slug"=>"testSlug",
"tag"=>"testTag",
"scope"=>1
]
];
$this->json('POST', '/settings/gdata/create', $json, $header)->seeJson([ 'status' => true ]);
}
}
But i can't get $json data with lumen Request and when i get that it's null.
I try in postman like this :
I fill just input password. Then I click button update request
The view like this :
This is header :
This is body. I select raw and input data like this :
Then I click button send and it can get the response
But when I try use guzzle 6 like this :
public function testApi()
{
$requestContent = [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json'
],
'json' => [
'auth' => ['', 'b0a619c152031d6ec735dabd2c6a7bf3f5faaedd'],
'ids' => ["1"]
]
];
try {
$client = new GuzzleHttpClient();
$apiRequest = $client->request('POST', 'https://myshop/api/order', $requestContent);
$response = json_decode($apiRequest->getBody());
dd($response);
} catch (RequestException $re) {
// For handling exception.
}
}
The result is empty
How can I solve this problem?
See in Postman, you correctly specify the field Authorization in the "headers" tab. So it sould be the same when you use Guzzle, put it in the headers:
public function testApi()
{
$requestContent = [
'auth' => ['username', 'password']
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
'json' => [
'ids' => ["1"]
]
];
try {
$client = new GuzzleHttpClient;
$apiRequest = $client->request('POST', 'https://myshop/api/order', $requestContent);
$response = json_decode($apiRequest->getBody());
dd($response);
} catch (RequestException $re) {
// For handling exception.
}
}
i trying to post data as Async with using Guzzle 6(latest ver)
$client = new Client();
$request = $client->postAsync($url, [
'json' => [
'company_name' => 'update Name'
],
]);
but i am not getting any request form Guzzle like post request on terminal
Because it's a promise, you need to put then
and the promise will not called unless you put $promise->wait()
This is a simple post request using postAsync based on your question:
$client = new Client();
$promise = $client->postAsync($url, [
'json' => [
'company_name' => 'update Name'
],
])->then(
function (ResponseInterface $res){
$response = json_decode($res->getBody()->getContents());
return $response;
},
function (RequestException $e) {
$response = [];
$response->data = $e->getMessage();
return $response;
}
);
$response = $promise->wait();
echo json_encode($response);
Have you tried to send the request?
http://guzzle.readthedocs.org/en/latest/index.html?highlight=async
$client = new Client();
$request = new Request('POST', $url, [
"json" => [
'company_name' => 'update Name']
]);
$promise = $client->sendAsync($request)->then(function ($response) {
echo 'I completed! ' . $response->getBody();
});
$promise->wait();
Guzzle 6 has very little practical examples/documentation for developers to refer. I am sharing an example on how to use postAsync and Pool object. This allows concurrent async requests using guzzle 6. ( I was not able to find a direct example and spent 2 days to get working code )
function postInBulk($inputs)
{
$client = new Client([
'base_uri' => 'https://a.b.com'
]);
$headers = [
'Authorization' => 'Bearer token_from_directus_user'
];
$requests = function ($a) use ($client, $headers) {
for ($i = 0; $i < count($a); $i++) {
yield function() use ($client, $headers) {
return $client->postAsync('https://a.com/project/items/collection', [
'headers' => $headers,
'json' => [
"snippet" => "snippet",
"rank" => "1",
"status" => "published"
]
]);
};
}
};
$pool = new Pool($client, $requests($inputs),[
'concurrency' => 5,
'fulfilled' => function (Response $response, $index) {
// this is delivered each successful response
},
'rejected' => function (RequestException $reason, $index) {
// this is delivered each failed request
},
]);
$pool->promise()->wait();
}
most likely you'll need to call wait();
$request->wait();