Currently I am authorizing the Amadeus API and receiving an access token through a service class, i.e. Amadeus\Client . There are several API endpoints for multiple functions, so I want to maintain separate classes for separate endpoints. Each endpoint requires access token to process the request. How can I transfer the authorization token form the Amadeus\Client to Amadeus\FlightOffersSearch so I can pass the access token into the headers of the endpoint. Please can someone help me here?
Amadeus\Client
class Client
{
public function __construct(
protected string $uri,
protected string $client_id,
protected string $client_secret,
protected string $grant_type,
) {}
public function authorization() {
$uri = $this->uri;
$auth_data = array(
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'grant_type' => $this->grant_type
);
$requests_response = Http::asForm()->post($uri, $auth_data);
$response_body = json_decode($requests_response->body());
$access_token = $response_body->access_token;
return $access_token;
}
Amadeus\FlightOffersSearch
class Flight
{
public function get_flight() {
if(isset($access_token)){
$endpoint = 'https://test.api.amadeus.com/v2/shopping/flight-offers';
$travel_data = array(
'originLocationCode' => 'BOS',
'destinationLocationCode' => 'PAR',
'departureDate' => '2022-06-14',
'adults' => 2
);
$params = http_build_query($travel_data);
$url = $endpoint.'?'.$params;
$headers = array('Authorization' => 'Bearer '.$access_token);
}
}
}
This is more PHP specific than Laravel. I suggest you make the client a singleton class where you encapsulate all the authentication logic and just get the client instance when you need it using a static method.
I also highly recommend using GuzzleHttp for HTTP client.
Something like this draft.
<?php
namespace App;
use GuzzleHttp\Client;
class AmadeusClient
{
private static $instance;
private $client;
private $token;
public function __construct()
{
throw_if(static::$instance, 'There should be only one instance of this class');
static::$instance = $this;
$this->client = new Client([
'base_uri' => 'https://test.api.amadeus.com/v2/',
]);
}
private function authenticate()
{
if (!$this->token) {
$client_id = env('api_client_id');
$client_secret = env('api_client_secret');
$grant_type = env('api_grant_type$a');
$this->token = ''; // TODO: get authorization token using $this->client;
}
}
public static function getInstance()
{
return static::$instance ?: (new static());
}
public function get($uri, array $options = [])
{
$this->authenticate();
return $this->client->get($uri, $options);
}
public function post($uri, array $options = [])
{
$this->authenticate();
return $this->client->post($uri, $options);
}
}
You would then use it like this:
$client = AmadeusClient::getInstance();
$client->get('shopping/flight-offers', $options);
Related
I am building a class wrapper around the themoviedb.org api. I'm using guzzle 7 for the requests, but it seems that it is not throwing any exception.
namespace App\Classes;
use App\Models\Movie;
use App\Models\Series;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\RequestInterface;
class TMDBScraper{
private string $apiKey;
private string $language;
private Client $client;
private const API_URL = "http://api.themoviedb.org/3/";
private const IMAGE_URL = "http://image.tmdb.org/t/p/";
private const POSTER_PATH_SIZE = "w500";
private const BACKDROP_PATH_SIZE = "original";
public function __construct(string $apiKey = "default_api_key") {
$this->apiKey = $apiKey;
$this->language = app()->getLocale();
$handlerStack = new HandlerStack(new CurlHandler());
$handlerStack->unshift(Middleware::mapRequest(function (RequestInterface $request) {
return $request->withUri(Uri::withQueryValues($request->getUri(), [
'api_key' => $this->apiKey,
'language' => $this->language
]));
}));
$this->client = new Client([
'base_uri' => self::API_URL,
'handler' => $handlerStack
]);
}
public function search($screenplayType, $query): ?array {
try {
$response = json_decode($this->client->get('search/' . $screenplayType, [
'query' => compact('query')
])->getBody());
return $this->toModel($response, $screenplayType);
} catch (GuzzleException $e) {
echo $e->getMessage();
return null;
}
}
... more code }
I tried to use a wrong api key, but the client exception is not thrown. I also tried to set http_errors to true, that should be set by default, but it didn't work too.
You can try this code:
$handler = new CurlHandler();
$stack = HandlerStack::create($handler);
I have an interface with search function:
interface Searcher
{
public function search($text,$limit)
}
I have some realization based on API
class APISeach implements Searcher
{
public function search($text,$limit)
{
$params = [
'name' => $sName,
'maxRows' => $iLimit,
];
$response = Http::get('http://some_api_service/search', $params)->json();
}
}
And I have some code which used this Search:
class MyController extends Controller
{
private $seacher;
public function __construct(Searcher $mySeacher)
{
$this->seacher = $mySeacher;
}
public function search($text,$limit=10)
{
$this->seacher->search($text,$limit)
}
}
All looks fine (may be). But what if I need change API realization and it will be required another parameters. For example:
class AnotherAPISeach implements Searcher
{
public function search($text,$language,$fuzzy,$limit)
{
$ApiObjectFromLib = new ApiObjectFromLib();
$response = $ApiObjectFromLib->search($text,$language,$fuzzy,$limit)->json();
}
}
So it's can not implement Searcher interface any more.
Is it exists any way to use interfaces for API functions? All API can required various parameters and it's no good if I need change Interface or Controller code for each API.
You can use variadic arguments
interface Searcher
{
public function search($text, $limit, ...$additional);
}
If that defeats the purpose of the interface is up to you to decide 😉
​​​​​​​
How about something like this (demo)?
public function search(string $name, int $limit = \Search\Limit::DEFAULT)
{
return $this->api->search(
new \Search\Name($name),
new \Search\Limit($limit)
);
}
public function lookup(
string $name,
string $language = \Search\Language::DEFAULT,
bool $fuzzy = \Search\Fuzzy::DEFAULT,
int $limit = \Search\Limit::DEFAULT
) {
return $this->api->search(
new \Search\Name($name),
new \Search\Language($language),
new \Search\Fuzzy($fuzzy),
new \Search\Limit($limit)
);
}
These "specifications" look like this:
namespace Search
{
interface Specification
{
public function __invoke(array $params): array;
}
class Name implements Specification
{
private $name = null;
public function __construct(string $name)
{
$this->name = $name;
}
public function __invoke(array $params): array
{
return [
'name' => $this->name,
];
}
}
class Language implements Specification
{
const DEFAULT = 'en';
private $language = null;
public function __construct(string $language)
{
$this->language = $language;
}
public function __invoke(array $params): array
{
return [
'language' => $this->language ?? 'en',
];
}
}
class Fuzzy implements Specification
{
const DEFAULT = true;
private $fuzzy = null;
public function __construct(bool $fuzzy)
{
$this->fuzzy = $fuzzy;
}
public function __invoke(array $params): array
{
return [
'fuzzy' => $this->fuzzy,
];
}
}
class Limit implements Specification
{
const DEFAULT = 10;
private $max = null;
public function __construct(int $limit)
{
$this->limit = $limit;
}
public function __invoke(array $params): array
{
return [
'maxRows' => $this->limit ?: self::DEFAULT,
];
}
}
}
Which the searchable API composes like this:
interface Searchable
{
public function search(\Search\Specification... $criteria);
}
class Search implements Searchable
{
private $url = '/search';
private $defaults = [
'maxRows' => \Search\Limit::DEFAULT,
];
public function search(\Search\Specification ...$criteria)
{
return \Http::get($this->url, array_reduce(
$criteria,
fn($params, $criteria) => $criteria($params) + $params,
$this->defaults
))->json();
}
}
The Specification Pattern is interesting, since it implies that a request into a domain is really just a chain of decisions that result in a configuration that can be applied elsewhere.
For instance, note how above the $criteria($params) objects are each given the current $params for the request, for which it may override parameters, read and modify a parameter, or potentially incorporate a Specification check to validate parameters.
Note on the array + array syntax, which is a way to merge arrays:
['foo' => 'bar'] + ['foo' => 'baz'] // left takes precedence: ['foo' => 'bar']
Filter/Criteria is very similar; I tend to think of those having a tighter link to the object it's applied to (Repository, Query or Collection) than Specification, which in my mind applies more to what's to be gotten back.
I am trying to convert aws to upload with wasabi api and it says it runs with aws so I am looking to put in my access key after instantiation but cant find the file in which the keys go into. On the other hand, If you know how to integrate wasabi s3 api into my website that would be a great help for me to know thank you.
aws/credientials/credientials.php
<?php
namespace Aws\Credentials;
/**
* Basic implementation of the AWS Credentials interface that allows callers to
* pass in the AWS Access Key and AWS Secret Access Key in the constructor.
*/
class Credentials implements CredentialsInterface, \Serializable
{
private $key;
private $secret;
private $token;
private $expires;
/**
* Constructs a new BasicAWSCredentials object, with the specified AWS
* access key and AWS secret key
*
* #param string $key AWS access key ID
* #param string $secret AWS secret access key
* #param string $token Security token to use
* #param int $expires UNIX timestamp for when credentials expire
*/
public function __construct($key, $secret, $token = null, $expires = null)
{
$this->key = trim($key);
$this->secret = trim($secret);
$this->token = $token;
$this->expires = $expires;
}
public static function __set_state(array $state)
{
return new self(
$state['key'],
$state['secret'],
$state['token'],
$state['expires']
);
}
public function getAccessKeyId()
{
return $this->key;
}
public function getSecretKey()
{
return $this->secret;
}
public function getSecurityToken()
{
return $this->token;
}
public function getExpiration()
{
return $this->expires;
}
public function isExpired()
{
return $this->expires !== null && time() >= $this->expires;
}
public function toArray()
{
return [
'key' => $this->key,
'secret' => $this->secret,
'token' => $this->token,
'expires' => $this->expires
];
}
public function serialize()
{
return json_encode($this->toArray());
}
public function unserialize($serialized)
{
$data = json_decode($serialized, true);
$this->key = $data['key'];
$this->secret = $data['secret'];
$this->token = $data['token'];
$this->expires = $data['expires'];
}
}
If you are using the AWS Command-Line Interface (CLI) in there somewhere, then the easiest method is to run:
aws configure
It will store the settings in ~/.aws/credentials.
Then, your code does not need to reference the credentials. The SDK will automatically retrieve the credentials from that file.
require('vendor/autoload.php');
use Aws\Ec2\Ec2Client;
$credentials = new Aws\Credentials\Credentials('Your Access Key',
'Your Secret Key'); // Place here both key
$ec2Client = new Aws\Ec2\Ec2Client([
'version' => 'latest',
'region' => 'ap-south-1',
'credentials' => $credentials
]);
$result = $ec2Client->describeKeyPairs();
echo '<pre>';
print_r($result);
Reference site : https://docs.aws.amazon.com/aws-sdk-php/v2/guide/credentials.html#passing-credentials-into-a-client-factory-method
I'm trying to setup soap server by following https://framework.zend.com/blog/2017-01-24-zend-soap-server.html tutorial.
My corresponding changes
<?php
namespace Soap\Controller;
use Soap\Model;
use Zend\Soap\AutoDiscover as WsdlAutoDiscover;
use Zend\Soap\Server as SoapServer;
use Zend\Mvc\Controller\AbstractActionController;
class SoapController extends AbstractActionController{
private $env;
public function __construct(Model\Env $env){
$this->env = $env;
}
public function wsdlAction(){
/** #var \Zend\Http\Request $request */
$request = $this->getRequest();
if (!$request->isGet()) {
return $this->prepareClientErrorResponse('GET');
}
$wsdl = new WsdlAutoDiscover();
$wsdl = new WsdlAutoDiscover();
$this->populateServer($wsdl);
/** #var \Zend\Http\Response $response */
$response = $this->getResponse();
$response->getHeaders()->addHeaderLine('Content-Type', 'application/wsdl+xml');
$response->setContent($wsdl->toXml());
return $response;
}
private function prepareClientErrorResponse($allowed){
/** #var \Zend\Http\Response $response */
$response = $this->getResponse();
$response->setStatusCode(405);
$response->getHeaders()->addHeaderLine('Allow', $allowed);
return $response;
}
private function populateServer($server){
// Expose a class and its methods:
$server->setClass(Model\Products::class);
// Expose an object instance and its methods:
$server->setObject($this->env);
// Expose a function:
$server->addFunction('Soap\Model\Env\ping');
$server->addFunction('Soap\Model\Env\pong');
}
public function serverAction(){
/** #var \Zend\Http\Request $request */
$request = $this->getRequest();
if (!$request->isPost()) {
return $this->prepareClientErrorResponse('POST');
}
// Create the server
$server = new SoapServer(
$this->url()
->fromRoute('soap/wsdl', [], ['force_canonical' => true]),
[
'actor' => $this->url()
->fromRoute('soap/server', [], ['force_canonical' => true]),
]
);
$server->setReturnResponse(true);
$this->populateServer($server);
$soapResponse = $server->handle($request->getContent());
/** #var \Zend\Http\Response $response */
$response = $this->getResponse();
// Set the headers and content
$response->getHeaders()->addHeaderLine('Content-Type', 'application/soap+xml');
$response->setContent($soapResponse);
return $response;
}
}
After running the server I'm getting error
Call to undefined method Zend\Soap\AutoDiscover::setObject()
When I checked in source of AutoDiscover there is no setObject, What changes do I do to fix it.
This is the populateServer method from the link you posted:
use Acme\Model;
function populateServer($server, array $env)
{
// Expose a class and its methods:
$server->setClass(Model\Calculator::class);
// Or expose an object instance and its methods.
// However, this only works for Zend\Soap\Server, not AutoDiscover, so
// should not be used here.
// $server->setObject(new Model\Env($env));
// Expose a function:
$server->addFunction('Acme\Model\ping');
}
And the note just below that:
[...] if you want to create logic that can be re-used between the Server and AutoDiscover instances, you must confine your usage to setClass(). If that class requires constructor arguments or other ways of setting instance state, you should vary the logic for creation of the WSDL via AutoDiscover and creation of the server via Server.
So, I want to create a client base URL with singleton.
This is my GuzzleClient.php which is containing the base URL
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class GuzzleClient {
public static function getClient()
{
static $client = null;
if (null === $client)
{
$client = new Client([
'base_url' => 'http://localhost:8080/task_manager/v1/',
]);
}
return $client;
}
private function __construct()
{}
}
And this one is where I should put the base url
require_once 'GuzzleClient.php';
class CardTypeAPIAccessor
{
private $client;
public function __construct($client)
{
$this->client = $client;
}
public function getCardTypes() {
$cardTypes = array();
try
{
//this is where base URL should be
$response = $client->get('admin/card/type',
['headers' => ['Authorization' => $_SESSION['login']['apiKey']]
]);
$statusCode = $response->getStatusCode();
// Check that the request is successful.
if ($statusCode == 200)
{
$error = $response->json();
foreach ($error['types'] as $type)
{
$cardType = new CardType();
$cardType->setId($type['card_type_id']);
$cardType->setCategory($type['category']);
array_push($cardTypes, $cardType);
}
}
}
}
}
I stuck with how to put the method in GuzzleClient into this code.
Thanks
I'm creating an abstract class for my webservices that send requests. The instance of guzzle is created in a singleton file:
use Guzzle\Http\Client;
class GuzzleSingleton
{
private static $_instance = null;
private static $baseUrl = '';
public static function getBaseUrl(): string
{
return self::$baseUrl;
}
private static function setBaseUrl($baseUrl): void
{
self::$baseUrl = $baseUrl;
}
public static function getInstance(string $apiUrl, ?int $apiPort, string $apiSuffix): ?Client
{
if (is_null(self::$_instance)) {
self::buildApiUrl($apiUrl, $apiPort, $apiSuffix);
self::$_instance = new Client(self::getBaseUrl());
} else {
self::buildApiUrl($apiUrl, $apiPort, $apiSuffix);
self::$_instance->setBaseUrl(self::getBaseUrl());
}
return self::$_instance;
}
private static function buildApiUrl(string $apiUrl, ?int $apiPort, string $apiSuffix): void
{
$apiDns = rtrim($apiUrl, '/');
$urlApi = (!is_null($apiPort)) ? sprintf('%s:%d', $apiDns, $apiPort) : $apiUrl;
$urlApi .= $apiSuffix;
self::setBaseUrl($urlApi);
}
}
My abstract class:
abstract class AbstractWebservice
{
public function __construct(string $apiUrl, ?int $apiPort, ?string $apiSuffixUrl, Session $session)
{
$this->setApiUrl($apiUrl);
$this->setApiPort($apiPort);
$this->setApiSuffixUrl($apiSuffixUrl);
$this->session = $session;
$this->buildFactory();
}
public function buildFactory(): void
{
$this->guzzleWs = GuzzleSingleton::getInstance($this->getApiUrl(), $this->getApiPort(), $this->getApiSuffixUrl());
}
public function sendRequest(string $endpoint, ?array $body, ?array $options, string $method)
{
$request = $this->guzzleWs->createRequest($method, $endPoint, self::$headers, $body, $options);
return $request->send();
}
}
inharit GuzzleClient class into CardTypeAPIAccessor, and check if $client is notinstanceof GuzzleClient then assign access object into $this->client
class CardTypeAPIAccessor extends GuzzleClient
{
private $client;
public function __construct($client)
{
if($client instanceof GuzzleClient){
$this->client = $client
}else{
$this->client = parent::getClient();
}
}
}