Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
58.62% |
17 / 29 |
CRAP | |
45.36% |
44 / 97 |
Shopify\Client | |
0.00% |
0 / 1 |
|
58.62% |
17 / 29 |
440.66 | |
45.36% |
44 / 97 |
call | |
0.00% |
0 / 1 |
90 | |
0.00% |
0 / 22 |
|||
callGraphql | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 11 |
|||
request | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
getPrevPage | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
hasPrevPage | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getNextPage | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
hasNextPage | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
parseLinkString | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
getHttpMethods | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
setShop | |
0.00% |
0 / 1 |
2.26 | |
60.00% |
3 / 5 |
|||
getApiVersion | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setApiVersion | |
0.00% |
0 / 1 |
3.14 | |
75.00% |
3 / 4 |
|||
getShop | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setRestApiUrl | |
100.00% |
1 / 1 |
2 | |
100.00% |
10 / 10 |
|||
getRestApiUrl | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setGraphqlApiUrl | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
getGraphqlApiUrl | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setRestApiHeaders | |
100.00% |
1 / 1 |
2 | |
100.00% |
4 / 4 |
|||
getRestApiHeaders | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setGraphqlApiHeaders | |
100.00% |
1 / 1 |
1 | |
100.00% |
4 / 4 |
|||
getGraphqlApiHeaders | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setApiKey | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getApiKey | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setApiPassword | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getApiPassword | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setApiSecretKey | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getApiSecretKey | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
setApiParams | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getApiParams | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
<?php | |
namespace Shopify; | |
use GuzzleHttp\Exception\RequestException; | |
use Shopify\Common\ClientInterface; | |
use Shopify\Exception\ApiException; | |
/** | |
* Class Client | |
* @package Shopify | |
*/ | |
class Client implements ClientInterface | |
{ | |
/** | |
* define constant for current Shopify api version | |
*/ | |
const SHOPIFY_API_VERSION = '2020-07'; | |
/** | |
* define rest api call | |
*/ | |
const REST_API = 'rest'; | |
/** | |
* define private app | |
*/ | |
const PRIVATE_APP = 'private'; | |
/** | |
* header parameter of shopify access token | |
*/ | |
const SHOPIFY_ACCESS_TOKEN = 'X-Shopify-Access-Token'; | |
/** | |
* denine response header pagination string | |
*/ | |
const PAGINATION_STRING = 'Link'; | |
/** | |
* response header parameter of shopify api limit | |
*/ | |
const API_CALL_RATE_LIMIT_HEADER = 'http_x_shopify_shop_api_call_limit'; | |
/** | |
* define graphQL api call | |
*/ | |
const GRAPHQL = 'graphql'; | |
/** | |
* Shopify graphql base url | |
* @var string | |
*/ | |
protected $graphql_api_url = "https://{shopify_domain}/admin/api/{version}/graphql.json"; | |
/** | |
* rest api url for custom/public app | |
* @var string | |
*/ | |
protected $rest_api_url = 'https://{shopify_domain}/admin/api/{version}/{resource}.json'; | |
/** | |
* Shopify domain name | |
* @var string | |
*/ | |
protected $shop; | |
/** | |
* Shopify api key | |
* @var string | |
*/ | |
protected $api_key; | |
/** | |
* Shopify password for private app | |
* @var string | |
*/ | |
protected $password; | |
/** | |
* Shopify shared secret key for private app | |
* @var string | |
*/ | |
protected $api_secret_key; | |
/** | |
* access token for public app | |
* @var string | |
*/ | |
protected $access_token; | |
/** | |
* array('version') | |
* @var array | |
*/ | |
protected $api_params; | |
/** | |
* Shopify api call url | |
* @var array | |
*/ | |
protected $base_urls; | |
/** | |
* get api header array according to private and public app for rest api | |
* @var array | |
*/ | |
protected $restApiRequestHeaders; | |
/** | |
* get api header array according to private and public app for graphql api | |
* @var array | |
*/ | |
protected $graphqlApiRequestHeaders; | |
/** | |
* Shopify api version | |
* @var string | |
*/ | |
protected $api_version; | |
/** | |
* get response header | |
* @var string | |
*/ | |
protected $next_page; | |
/** | |
* get response header | |
* @var string | |
*/ | |
protected $prev_page; | |
/** | |
* static variable to api is going to reach | |
* @var bool | |
*/ | |
protected static $wait_next_api_call = false; | |
/** | |
* prepare data for rest api request | |
* @param $method | |
* @param $path | |
* @param array $params | |
* @return array | |
* @throws ApiException | |
* @throws ClientException | |
*/ | |
public function call($method, $path , array $params = []) | |
{ | |
$url = $this->getRestApiUrl(); | |
$options = []; | |
$allowed_http_methods = $this->getHttpMethods(); | |
if(!in_array($method, $allowed_http_methods)){ | |
throw new ApiException(implode(",",$allowed_http_methods)." http methods are allowed.",0); | |
} | |
if(is_array($this->getRestApiHeaders()) && count($this->getRestApiHeaders())) { | |
$options['headers'] = $this->getRestApiHeaders(); | |
} | |
$url=strtr($url, [ | |
'{resource}' => $path, | |
]); | |
if(in_array($method,['GET','DELETE'])) { | |
$options['query'] = $params; | |
}else { | |
$options['json'] = $params; | |
} | |
if(self::$wait_next_api_call) | |
{ | |
usleep(1000000 * rand(3, 6)); | |
} | |
$http_response = $this->request($method,$url,$options); | |
if (strtoupper($method) === 'GET' && $http_response->getHeaderLine(self::PAGINATION_STRING)) { | |
$this->next_page = $this->parseLinkString($http_response->getHeaderLine(self::PAGINATION_STRING),'next'); | |
$this->prev_page = $this->parseLinkString($http_response->getHeaderLine(self::PAGINATION_STRING),'previous'); | |
} | |
if($http_response->getHeaderLine(self::API_CALL_RATE_LIMIT_HEADER)) { | |
list($api_call_requested, $api_call_Limit) = explode('/', $http_response->getHeaderLine(self::API_CALL_RATE_LIMIT_HEADER)); | |
static::$wait_next_api_call = $api_call_requested / $api_call_Limit >= 0.8; | |
} | |
return \GuzzleHttp\json_decode($http_response->getBody()->getContents(),true); | |
} | |
/** | |
* prepare data for graphql api request | |
* @param string $query | |
* @return mixed|void | |
* @throws ApiException | |
* @throws ClientException | |
*/ | |
public function callGraphql($query) | |
{ | |
$url = $this->getGraphqlApiUrl(); | |
$options = []; | |
if(is_array($this->getRestApiHeaders()) && count($this->getRestApiHeaders())) { | |
$options['headers'] = $this->getRestApiHeaders(); | |
} | |
$options['body'] = $query; | |
$http_response = $this->request('POST', $url, $options); | |
$response = \GuzzleHttp\json_decode($http_response->getBody()->getContents(),true); | |
if(isset($response['errors'])) | |
{ | |
$http_bad_request_code = 400; | |
throw new ApiException(\GuzzleHttp\json_encode($response['errors']),$http_bad_request_code); | |
} | |
return $response; | |
} | |
/** | |
* send http request | |
* @param string $method | |
* @param string $url | |
* @param array $options | |
* @return array|mixed | |
* @throws ApiException | |
*/ | |
public function request($method,$url,array $options) | |
{ | |
try | |
{ | |
$client = new \GuzzleHttp\Client(); | |
return $client->request($method, $url, $options); | |
} | |
catch (RequestException $e) | |
{ | |
if(!empty($e->getResponse()->getBody()->getContents())) | |
{ | |
$json_error = json_decode($e->getResponse()->getBody()->getContents(),true); | |
$error_message = isset($json_error['errors'])?$json_error['errors']:\GuzzleHttp\json_encode($json_error); | |
} | |
else { | |
$error_message = $e->getMessage(); | |
} | |
throw new ApiException($error_message,$e->getCode()); | |
} | |
} | |
/** | |
* get previous page_info for any resource(products/orders) | |
* @return string | |
*/ | |
public function getPrevPage() | |
{ | |
return $this->prev_page; | |
} | |
/** | |
* check previous page_info for any resource(products/orders) | |
* @return string | |
*/ | |
public function hasPrevPage() | |
{ | |
return !empty($this->prev_page); | |
} | |
/** | |
* get next page_info for any resource(products/orders) | |
* @return string | |
*/ | |
public function getNextPage(){ | |
return $this->next_page; | |
} | |
/** | |
* check next page_info for any resource(products/orders) | |
* @return string | |
*/ | |
public function hasNextPage(){ | |
return !empty($this->next_page); | |
} | |
/** | |
* parse header string for previous and next page_info | |
* @param $pagination_string | |
* @param $page_link | |
* @return string | |
*/ | |
public function parseLinkString($pagination_string,$page_link) | |
{ | |
$matches = []; | |
preg_match("/<(.*page_info=([a-z0-9\-]+).*)>; rel=\"?{$page_link}\"?/i", $pagination_string, $matches); | |
return isset($matches[2]) ? $matches[2] : NULL; | |
} | |
/** | |
* return allowed http api methods | |
* @return array | |
*/ | |
public function getHttpMethods() | |
{ | |
return ['POST', 'PUT','GET', 'DELETE']; | |
} | |
/** | |
* set shopify domain | |
* @param $shop | |
* Exception for invalid shop name | |
* @throws ApiException | |
*/ | |
public function setShop($shop) | |
{ | |
if (!preg_match('/^[a-zA-Z0-9\-]{3,100}\.myshopify\.(?:com|io)$/', $shop)) { | |
throw new ApiException( | |
'Shop name should be 3-100 letters, numbers, or hyphens eg mypetstore.myshopify.com',0 | |
); | |
} | |
$this->shop = $shop; | |
} | |
/** | |
* return latest api version | |
* @return string | |
*/ | |
public function getApiVersion() | |
{ | |
return $this->api_version; | |
} | |
/** | |
* set api version | |
* @param api_version | |
* Exception for valid value | |
* @throws ApiException | |
*/ | |
public function setApiVersion($ap_params) | |
{ | |
$this->api_version = !empty($ap_params['version'])?$ap_params['version']:self::SHOPIFY_API_VERSION; | |
if (!preg_match('/^[0-9]{4}-[0-9]{2}$|^unstable$/', $this->api_version)) | |
{ | |
throw new ApiException('Api Version must be of YYYY-MM or unstable',0); | |
} | |
} | |
/** | |
* return Shopify domain | |
* @return string | |
*/ | |
public function getShop() | |
{ | |
return $this->shop; | |
} | |
/** | |
* set rest api url | |
* @param $rest_api_url | |
* @param $app_type | |
*/ | |
public function setRestApiUrl($rest_api_url, $app_type = '') | |
{ | |
if($app_type == self::PRIVATE_APP) | |
{ | |
$this->rest_api_url = strtr($rest_api_url, [ | |
'{api_key}' => $this->api_key, | |
'{password}' => $this->password, | |
'{shopify_domain}' => $this->shop, | |
'{version}' => $this->getApiVersion(), | |
]); | |
}else{ | |
$this->rest_api_url = strtr($rest_api_url, [ | |
'{shopify_domain}' => $this->shop, | |
'{version}' => $this->getApiVersion(), | |
]); | |
} | |
} | |
/** | |
* get rest api url app | |
* @return string | |
*/ | |
public function getRestApiUrl(){ | |
return $this->rest_api_url; | |
} | |
/** | |
* set graphql api url | |
* @param $graphql_api_url | |
*/ | |
public function setGraphqlApiUrl($graphql_api_url) | |
{ | |
$this->graphql_api_url = strtr($graphql_api_url, [ | |
'{shopify_domain}' => $this->shop, '{version}' => $this->getApiVersion() | |
]); | |
} | |
/** | |
* get graphql api url | |
* @return string | |
*/ | |
public function getGraphqlApiUrl(){ | |
return $this->graphql_api_url; | |
} | |
/** | |
* set rest api headers | |
* @return string | |
*/ | |
public function setRestApiHeaders($access_token = ''){ | |
$this->restApiRequestHeaders['Content-Type'] = "application/json"; | |
if($access_token){ | |
$this->restApiRequestHeaders[self::SHOPIFY_ACCESS_TOKEN] = $this->access_token; | |
} | |
} | |
/** | |
* get rest api headers | |
* @return array | |
*/ | |
public function getRestApiHeaders(){ | |
return $this->restApiRequestHeaders; | |
} | |
/** | |
* get graphql api url | |
* @return string | |
*/ | |
public function setGraphqlApiHeaders($access_token){ | |
$this->graphqlApiRequestHeaders['Content-Type'] = "application/graphql"; | |
$this->graphqlApiRequestHeaders['X-GraphQL-Cost-Include-Fields'] = true; | |
$this->graphqlApiRequestHeaders[self::SHOPIFY_ACCESS_TOKEN] = $access_token; | |
} | |
/** | |
* get graphql api url | |
* @return string | |
*/ | |
public function getGraphqlApiHeaders(){ | |
return $this->graphqlApiRequestHeaders; | |
} | |
/** | |
* set api_key of public or private app | |
* @param $api_key | |
*/ | |
public function setApiKey($api_key){ | |
$this->api_key = $api_key; | |
} | |
/** | |
* get api_key of public or private app | |
* @return string | |
*/ | |
public function getApiKey(){ | |
return $this->api_key; | |
} | |
/** | |
* set api_key of public or private app | |
* @param $api_password | |
*/ | |
public function setApiPassword($api_password){ | |
$this->password = $api_password; | |
} | |
/** | |
* get api_password of private app | |
* @return string | |
*/ | |
public function getApiPassword(){ | |
return $this->password; | |
} | |
/** | |
* set api secret key for public app | |
* @param $api_secret_key | |
*/ | |
public function setApiSecretKey($api_secret_key){ | |
$this->api_secret_key = $api_secret_key; | |
} | |
/** | |
* get api secret key for public app | |
* @return string | |
*/ | |
public function getApiSecretKey(){ | |
return $this->api_secret_key; | |
} | |
/** | |
* set api_params of public or private app | |
* @param $api_params | |
*/ | |
public function setApiParams($api_params){ | |
$this->api_params = $api_params; | |
} | |
/** | |
* get api_params of public or private app | |
* @return string | |
*/ | |
public function getApiParams(){ | |
return $this->api_params; | |
} | |
} |