Traits or services for controller - php

I have a method and variable which i want to be used on multiple controllers. I'm not too sure whether to use traits or services as both of them to my understanding serve almost the same purpose?
private $app_id;
private $archiving;
public function setArchieve()
{
$this->app_id = config('archiving.id');
$this->archiving = Application::findOrFail($this->app_id);
}
public function guzzleClient() {
$headers = [
'Content-Type' => 'application/json',
'X-Requested-With' => 'XMLHttpRequest',
'Authorization' => 'Bearer ' . $this->archiving->token,
];
$client = new \GuzzleHttp\Client([
'headers' => $headers,
'http_errors' => false,
'base_uri' => $this->archiving->url . '/api/',
]);
return $client;
}

Related

Does Guzzle Client inherit Client from handler?

Using PHP 8.1, Laravel 9, Guzzle 7.4.2.
I have this snippet in a service singleton registered in AppServiceProvider:
$middleware = new AccessTokenGuzzleMiddleware(
client: new Client([
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]),
cache: $this->app->make('cache')->store(),
tokenUrl: config('spica.url') . 'auth',
clientId: config('spica.space_api.client_id'),
clientSecret: config('spica.space_api.key'),
);
$stack = HandlerStack::create();
$stack->push($middleware);
$spaceClient = new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'handler' => $stack,
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]);
I can't test myself, because I have no access to base_uri, and it will only be tested once on development server, but I am curious if the Client inherits anything from the handler stack, and how does it do so if there are multiple middlewares stacked in handler?
Asking because all the examples in Guzzle docs create the final client with just $client = new Client(['handler' => $stack]);.
As you can see I currently repeat base_uri and verify lines.
Would the following work the same?
$middleware = new AccessTokenGuzzleMiddleware(
client: new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]),
cache: $this->app->make('cache')->store(),
tokenUrl: config('spica.url') . 'auth',
clientId: config('spica.space_api.client_id'),
clientSecret: config('spica.space_api.key'),
);
$stack = HandlerStack::create();
$stack->push($middleware);
$spaceClient = new Client(['handler' => $stack]);
For completeness, here's the AccessTokenGuzzleMiddleware class:
<?php
declare(strict_types=1);
namespace App\Support;
use GuzzleHttp\Client;
use Psr\Http\Message\RequestInterface;
use Illuminate\Contracts\Cache\Repository as CacheContract;
/**
* This is a Guzzle middleware (see more at https://docs.guzzlephp.org/en/stable/handlers-and-middleware.html).
* It adds "Authorization: Bearer" header to requests.
*/
class AccessTokenGuzzleMiddleware
{
public function __construct(
private Client $client,
private CacheContract $cache,
private string $tokenUrl,
private string $clientId,
private string $clientSecret
) {
}
public function __invoke(callable $handler)
{
return function (RequestInterface $request, array $options) use ($handler) {
$request = $request->withHeader('Authorization', 'Bearer ' . $this->getAccessToken());
return $handler($request, $options);
};
}
/**
* Defines cache key for access token.
*
* #return string
*/
private function cacheKey(): string
{
return self::class . $this->tokenUrl . $this->clientId . $this->clientSecret;
}
/**
* Get access token from cache or external API.
*
* #return string
*/
private function getAccessToken(): string
{
$cacheKey = $this->cacheKey();
if ($this->cache->has($cacheKey)) {
return $this->cache->get($cacheKey);
}
$tokenData = $this->retrieveClientAccessTokenData();
$this->cache->set(
key: $cacheKey,
value: $tokenData->access_token,
ttl: $tokenData->expires_in - 10
);
return $tokenData->access_token;
}
/**
* This is effectively the client (this service, not on behalf of a user)
* authenticating itself.
*/
private function retrieveClientAccessTokenData(): object
{
$authResponse = $this->client->post($this->tokenUrl, [
'json' => [
'apiKey' => $this->clientSecret,
'client_id' => $this->clientSecret,
],
]);
assertResponseOk($authResponse);
return json_decode($authResponse->getBody()->getContents());
}
}
EDIT 1
Until I get a better answer, I went with the following:
$this->app->singleton(KeycloakService::class, function (Application $app) {
// Log all Guzzle HTTP requests and responses.
// https://github.com/gmponos/guzzle-log-middleware#advanced-initialization
$middleware = new OAuthGuzzleMiddleware(
client: new Client(['verify' => config('keycloak.verify_ssl')]),
cache: $this->app->make('cache')->store(),
tokenUrl: config('keycloak.url') . 'auth/realms/' . config('keycloak.realm') . '/protocol/openid-connect/token',
clientId: config('keycloak.client_id'),
clientSecret: config('keycloak.client_secret'),
);
$stack = HandlerStack::create();
$stack->push($middleware);
$oAuthKeycloakClient = new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'handler' => $stack,
'base_uri' => config('keycloak.url') . 'auth/admin/realms/' . config('keycloak.realm') . '/',
'verify' => config('keycloak.verify_ssl'),
]);
return new KeycloakService(
client: $oAuthKeycloakClient,
groupHandler: new GroupHandler(),
userHandler: new UserHandler(),
);
});
$this->app->singleton(SpicaService::class, function (Application $app) {
// Time API
$timeClient = new Client([
'base_uri' => config('spica.url') . config('spica.time_api.path'),
'headers' => [
'Content-Type' => 'application/x-www-form-urlencoded',
'Accept' => 'application/json',
'Authorization' => config('spica.time_api.auth_token'),
],
'verify' => config('spica.verify_ssl'),
]);
// Space API
$middleware = new AccessTokenGuzzleMiddleware(
client: new Client(['verify' => config('spica.verify_ssl')]),
cache: $this->app->make('cache')->store(),
tokenUrl: config('spica.url') . 'auth',
clientId: config('spica.space_api.client_id'),
clientSecret: config('spica.space_api.key'),
);
$stack = HandlerStack::create();
$stack->push($middleware);
$spaceClient = new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'handler' => $stack,
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]);
// Create service
return new SpicaService(
timeClient: $timeClient,
spaceClient: $spaceClient,
employeeHandler: new EmployeeHandler(),
);
});
That's not really how Guzzle works. So there's three concepts in Guzzle that you're trying to mix up, Client, Middleware and Handler which I'll try to explain.
With the Client you build the request and prepare it for the middleware/handler, this is where you define what the request is.
The Handler executes the request, note: when you create a Handlerstack::create() the default handler will be the CurlHandler, you can overwrite the handler here as a parameter
The Middleware is used to modify the request or response after the request is executed. But the request will eventually be passed to the handler to execute it.
What you can do is provide the client as a service in the AppServiceProvider
$this->app->bind(Client::class, function () {
return new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]);
});
That way everywhere the Client is injected in the constructor it will inject that pre-configured client
Edit:
Just realised that would miss the handler in some cases, you could provide it as a singleton and overwrite the client when you've authenticated
Edit with contextual binding:
$this->app->when(KeycloakService::class)
->needs(Client::class)
->give(function () {
return new Client();
});
$this->app->when(KeycloakService::class)
->needs(Client::class)
->give(function () {
return new Client();
});
$this->app->when(AccessTokenGuzzleMiddleware::class)
->needs(Client::class)
->give(function () {
return new Client([
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'base_uri' => config('spica.url') . config('spica.space_api.path'),
'verify' => config('spica.verify_ssl'),
]);
});

How to pass id parameter using php laravel?

I want to pass the id from the controller to route but I'm having trouble with it. I am a newbie at this so I would really appreciate your help!
Controller:
public function fetchVideo(Request $request)
{
$client = new Client();
$input = $request->all();
$headers = [
'Authorization' => 'Bearer '.$input['token'],
'Content-type' => 'application/json',
'Accept' => 'application/json'
];
$params = [
'id' => $input['id'],
'fields' => $input['fields']
];
$response = $client->request ('GET', 'https://api.dailymotion.com/video/{id}', [
'headers' => $headers,
'query' => $params
]);
return json_decode($response->getBody(), true);
}
Route:
Route::post('/video/{id}', 'App\Http\Controllers\dailymotionController#fetchVideo');
public function fetchVideo(Request $request, $id) // <- {id} parameter in the route
{
...
$response = $client->request('GET', "https://api.dailymotion.com/video/{$id}", [...])
...
}

What is the best design pattern to consume REST API

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;
}

How can I set an API key once and save it within a function?

I am working on some classes and functions. The functions fetch data from an API which requires an API key. Is it possible to set an API key once and then use it throughout the program?
// Class example
class Airport
{
public function setClient($appId, $appKey)
{
$client = new GuzzleHttp\Client([
'headers' => array(
'resourceversion' => 'v4',
'accept' => 'application/json',
'app_id' => $appId, // Set this
'app_key' => $appKey // And this
)
]);
}
}
// Other file example
require 'classes.php';
$airport = new Airport();
$airport->setClient('xxxxxxxxxxx', 'xxxxxxxx');
// Continue to use other functions without setting the API key again.
You can save them as properties using $this
I'm not sure if you want to reuse the client or the app id/key, but it's pretty much the same idea either way.
// Class example
class Airport
{
private $appId;
private $appKey;
private $client;
public function setClient($appId, $appKey)
{
$this->appId = $appId;
$this->appKey = $appKey;
$this->client = new GuzzleHttp\Client([
'headers' => array(
'resourceversion' => 'v4',
'accept' => 'application/json',
'app_id' => $this->appId, // Set this
'app_key' => $this->appKey // And this
)
]);
}
// New function that uses the client
public function someOtherMethod()
{
$x = $this->client->someMethod();
}
// new function that uses the app properties
public function anotherMethod()
{
$x = new Something($this->appId, $this->appKey);
}
}

How to get response from guzzle use auth and body raw?

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.
}
}

Categories