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);
Related
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);
How do I get the PDO connection in my model class that was created in dependicies.php?
I have Controller and Model classes.
My route:
$app->group('/users', function (Group $group) {
$group->get('', [UsersController::class, 'getAll'], function (Request $request, Response $response) {
return $response;
});
});
My Controller:
namespace App\Application\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use App\Application\Models\UsersService;
class UsersController
{
private $_usersSvc;
public function __construct()
{
$this->_usersSvc = new UsersService();
}
public function getAll(Request $request, Response $response)
{
$uri = $request->getUri();
parse_str($uri->getQuery(), $params);
$result = $this->_usersSvc->getAll($params);
$response->getBody()->write(json_encode($result));
return $response;
}
My DI setup in dependencies.php
return function (ContainerBuilder $containerBuilder) {
$containerBuilder->addDefinitions([
LoggerInterface::class => function (ContainerInterface $c) {
$settings = $c->get(SettingsInterface::class);
$loggerSettings = $settings->get('logger');
$logger = new Logger($loggerSettings['name']);
$processor = new UidProcessor();
$logger->pushProcessor($processor);
$handler = new StreamHandler($loggerSettings['path'], $loggerSettings['level']);
$logger->pushHandler($handler);
return $logger;
},
PDO::class => function (ContainerInterface $c) {
$settings = $c->get('settings');
$db = [
'dbname' => $settings['db']['name'],
'user' => $settings['db']['username'],
'pass' => $settings['db']['password'],
'host' => $settings['db']['host']
];
$connection = new PDO("mysql:host=" . $db['host'] . ";port=3306;dbname=" . $db['dbname'], $db['user'], $db['pass']);
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$connection->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
return $connection;
},
]);
};
My Model Class
namespace App\Application\Models;
use App\Application\Models\DataObjects;
use Psr\Log\LoggerInterface;
use Psr\Container\ContainerInterface;
use DateTime;
use PDO;
class UsersService extends DataObjects
{
protected $db;
public function __construct()
{
}
public function getAll($params)
{
$orderBy = (isset($params['sortdesc']) && empty($params['sortdesc']) === false) ? $params['sortdesc'] . ' DESC' : null;
if ($orderBy === null) {
$orderBy = (isset($params['sortasc']) && empty($params['sortasc']) === false) ? $params['sortasc'] . ' ASC' : '';
}
return $this->loadAll($orderBy);
}
}
How to access $connection instance from dependencies.php in my model class?
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.
What is the best way to have one instance of some object in class? I'm using yii2-httpclient and use Client class. I have different methods thank make requests for differents web API but I think its bad idea to creating new object when calling method. I'm trying make instance in constructor but have error "Call to a member function createRequest() on a non-object". How can I do this another way?
My Controller:
<?php
namespace app\models;
use yii;
use yii\base\Model;
use yii\httpclient\Client;
class ExchangeRates extends Model
{
function __construct()
{
$client = new Client();
}
public function getPrivatRate()
{
// $client = new Client();
$privatResponse = $client->createRequest()
->setMethod('get')
->setUrl('https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=5')
->send();
return json_decode($privatResponse->content, true);
}
public function getNationalRate()
{
// $client = new Client();
$nbuResponse = $client->createRequest()
->setMethod('get')
->setUrl('https://bank.gov.ua/NBUStatService/v1/statdirectory/exchange?valcode=USD&date=20161101&json')
->send();
return json_decode($nbuResponse->content, true);
}
public function getMejBankRate()
{
// $client = new Client();
$mejResponse = $client->createRequest()
->setMethod('get')
->setUrl('http://api.cashex.com.ua/api/v1/exchange/mejbank')
->send();
return json_decode($mejResponse->content, true);
}
public function getBlackMarketRate()
{
// $client = new Client();
$blackMarketResponse = $client->createRequest()
->setMethod('get')
->setUrl('http://api.cashex.com.ua/api/v1/exchange/black-market')
->send();
return json_decode($blackMarketResponse->content, true);
}
}
You can something like this:
class ExchangeRates extends Model
{
public $client;
public function init()
{
parent::init();
$this->client = new Client();
}
Now you can use it like this:
$privatResponse = $this->client->createRequest()->...
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();
}
}
}