diff --git a/README.md b/README.md index 766654b..085d2f1 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ $config = array( 'Password' => '***YOUR-PRIVATE-API-PASSWORD***', ); -PHPShopify\ShopifySDK::config($config); +$shopifySdk = new PHPShopify\ShopifySDK($config); ``` For Third party apps, use the permanent access token. @@ -54,7 +54,7 @@ $config = array( 'AccessToken' => '***ACCESS-TOKEN-FOR-THIRD-PARTY-APP***', ); -PHPShopify\ShopifySDK::config($config); +$shopifySdk = new PHPShopify\ShopifySDK($config); ``` You can use specific Shopify API Version by adding in the config array @@ -79,7 +79,7 @@ $config = array( 'SharedSecret' => '***YOUR-SHARED-SECRET***', ); -PHPShopify\ShopifySDK::config($config); +$shopifySdk = new PHPShopify\ShopifySDK($config); ``` 2) Create the authentication request @@ -93,21 +93,21 @@ $scopes = 'read_products,write_products,read_script_tags,write_script_tags'; //$scopes = array('read_products','write_products','read_script_tags', 'write_script_tags'); $redirectUrl = 'https://yourappurl.com/your_redirect_url.php'; -\PHPShopify\AuthHelper::createAuthRequest($scopes, $redirectUrl); +\PHPShopify\AuthHelper::createAuthRequest($shopifySdk, $scopes, $redirectUrl); ``` > If you want the function to return the authentication url instead of auto-redirecting, you can set the argument `$return` (5th argument) to `true`. ```php -\PHPShopify\AuthHelper::createAuthRequest($scopes, $redirectUrl, null, null, true); +\PHPShopify\AuthHelper::createAuthRequest($shopifySdk, $scopes, $redirectUrl, null, null, true); ``` 3) Get the access token when redirected back to the `$redirectUrl` after app authorization. ```php //your_redirect_url.php -PHPShopify\ShopifySDK::config($config); -$accessToken = \PHPShopify\AuthHelper::getAccessToken(); +$shopifySdk = PHPShopify\ShopifySDK($config); +$accessToken = \PHPShopify\AuthHelper::getAccessToken($shopifySdk); //Now store it in database or somewhere else ``` @@ -115,8 +115,8 @@ $accessToken = \PHPShopify\AuthHelper::getAccessToken(); ```php //your_authorize_and_redirect_url.php -PHPShopify\ShopifySDK::config($config); -$accessToken = \PHPShopify\AuthHelper::createAuthRequest($scopes); +$shopifySdk = new PHPShopify\ShopifySDK($config); +$accessToken = \PHPShopify\AuthHelper::createAuthRequest($shopifySdk, $scopes); //Now store it in database or somewhere else ``` diff --git a/lib/AuthHelper.php b/lib/AuthHelper.php index 101f903..2e7f0f4 100644 --- a/lib/AuthHelper.php +++ b/lib/AuthHelper.php @@ -41,7 +41,7 @@ public static function getCurrentUrl() * * @param array $data Data array * - * @return array + * @return string */ public static function buildQueryString($data) { @@ -49,25 +49,28 @@ public static function buildQueryString($data) foreach ($data as $key => $value) { $paramStrings[] = "$key=$value"; } - return join('&', $paramStrings); + return implode('&', $paramStrings); } /** * Verify if the request is made from shopify using hmac hash value * + * @param ShopifySDK $shopifySdk + * * @throws SdkException if SharedSecret is not provided or hmac is not found in the url parameters * * @return bool */ - public static function verifyShopifyRequest() + public static function verifyShopifyRequest($shopifySdk) { $data = $_GET; + $config = $shopifySdk->getConfig(); - if(!isset(ShopifySDK::$config['SharedSecret'])) { + if(!isset($config['SharedSecret'])) { throw new SdkException("Please provide SharedSecret while configuring the SDK client."); } - $sharedSecret = ShopifySDK::$config['SharedSecret']; + $sharedSecret = $config['SharedSecret']; //Get the hmac and remove it from array if (isset($data['hmac'])) { @@ -88,9 +91,9 @@ public static function verifyShopifyRequest() //hash the values before comparing (to prevent time attack) if(md5($realHmac) === md5($hmac)) { return true; - } else { - return false; } + + return false; } /** @@ -98,6 +101,7 @@ public static function verifyShopifyRequest() * * @see https://help.shopify.com/api/guides/authentication/oauth#scopes For allowed scopes * + * @param ShopifySDK $shopifySdk * @param string|string[] $scopes Scopes required by app * @param string $redirectUrl * @param string $state @@ -107,9 +111,9 @@ public static function verifyShopifyRequest() * * @return void|string */ - public static function createAuthRequest($scopes, $redirectUrl = null, $state = null, $options = null, $return = false) + public static function createAuthRequest($shopifySdk, $scopes, $redirectUrl = null, $state = null, $options = null, $return = false) { - $config = ShopifySDK::$config; + $config = $shopifySdk->getConfig(); if(!isset($config['ShopUrl']) || !isset($config['ApiKey'])) { throw new SdkException("ShopUrl and ApiKey are required for authentication request. Please check SDK configuration!"); @@ -122,10 +126,10 @@ public static function createAuthRequest($scopes, $redirectUrl = null, $state = //If redirect url is the same as this url, then need to check for access token when redirected back from shopify if(isset($_GET['code'])) { - return self::getAccessToken($config); - } else { - $redirectUrl = self::getCurrentUrl(); + return self::getAccessToken($shopifySdk); } + + $redirectUrl = self::getCurrentUrl(); } if (is_array($scopes)) { @@ -152,13 +156,15 @@ public static function createAuthRequest($scopes, $redirectUrl = null, $state = * Get Access token for the API * Call this when being redirected from shopify page ( to the $redirectUrl) after authentication * + * @param ShopifySDK $shopifySdk + * * @throws SdkException if SharedSecret or ApiKey is missing in SDK configuration or request is not valid * * @return string */ - public static function getAccessToken() + public static function getAccessToken($shopifySdk) { - $config = ShopifySDK::$config; + $config = $shopifySdk->getConfig(); if(!isset($config['SharedSecret']) || !isset($config['ApiKey'])) { throw new SdkException("SharedSecret and ApiKey are required for getting access token. Please check SDK configuration!"); @@ -178,8 +184,8 @@ public static function getAccessToken() } return isset($response['access_token']) ? $response['access_token'] : null; - } else { - throw new SdkException("This request is not initiated from a valid shopify shop!"); } + + throw new SdkException("This request is not initiated from a valid shopify shop!"); } } diff --git a/lib/HttpRequestJson.php b/lib/HttpRequestJson.php index c325b74..d1f98f5 100644 --- a/lib/HttpRequestJson.php +++ b/lib/HttpRequestJson.php @@ -169,7 +169,7 @@ public static function processRequest($method, $url) { * @return bool */ public static function shouldRetry($response, $error, $retry) { - $config = ShopifySDK::$config; + $config = []; if (isset($config['RequestRetryCallback'])) { return $config['RequestRetryCallback']($response, $error, $retry); diff --git a/lib/ShopifyResource.php b/lib/ShopifyResource.php index dd3b2cb..e82434c 100644 --- a/lib/ShopifyResource.php +++ b/lib/ShopifyResource.php @@ -135,12 +135,15 @@ abstract class ShopifyResource * @var string $prevLink */ private $prevLink = null; + private $config; - public function __construct($id = null, $parentResourceUrl = '') + public function __construct($config, $id = null, $parentResourceUrl = null) { - $this->id = $id; + $this->config = $config; + + $parentResourceUrl = $parentResourceUrl ?: ''; - $config = ShopifySDK::$config; + $this->id = $id; $this->resourceUrl = ($parentResourceUrl ? $parentResourceUrl . '/' : $config['ApiUrl']) . $this->getResourcePath() . ($this->id ? '/' . $this->id : ''); @@ -210,7 +213,7 @@ public function __call($name, $arguments) $resourceID = !empty($arguments) ? $arguments[0] : null; - $api = new $childClass($resourceID, $this->resourceUrl); + $api = new $childClass($this->config, $resourceID, $this->resourceUrl); return $api; } else { diff --git a/lib/ShopifySDK.php b/lib/ShopifySDK.php index 8fd83b1..99488b6 100644 --- a/lib/ShopifySDK.php +++ b/lib/ShopifySDK.php @@ -176,7 +176,7 @@ class ShopifySDK * * @var string[] */ - protected $resources = array( + protected static $resources = array( 'AbandonedCheckout', 'AssignedFulfillmentOrder', 'AccessScope', @@ -231,12 +231,12 @@ class ShopifySDK /** * @var float microtime of last api call */ - public static $microtimeOfLastApiCall; + public $microtimeOfLastApiCall; /** * @var float Minimum gap in seconds to maintain between 2 api calls */ - public static $timeAllowedForEachApiCall = .5; + public $timeAllowedForEachApiCall = .5; /** * @var string Default Shopify API version @@ -248,15 +248,14 @@ class ShopifySDK * * @var array */ - public static $config = array( - ); + protected $config = array(); /** * List of resources which are only available through a parent resource * * @var array Array key is the child resource name and array value is the parent resource name */ - protected $childResources = array( + protected static $childResources = array( 'Article' => 'Blog', 'Asset' => 'Theme', 'Balance' => 'ShopifyPayment', @@ -286,11 +285,25 @@ class ShopifySDK */ public function __construct($config = array()) { - if(!empty($config)) { - ShopifySDK::config($config); + $this->config = self::buildConfig($config); + + if (isset($this->config['AllowedTimePerCall'])) { + $this->timeAllowedForEachApiCall = $this->config['AllowedTimePerCall']; + } + + if (isset($config['Curl']) && is_array($config['Curl'])) { + CurlRequest::config($config['Curl']); } } + /** + * @return array Config + */ + public function getConfig() + { + return $this->config; + } + /** * Return ShopifyResource instance for a resource. * @example $shopify->Product->get(); //Returns all available Products @@ -319,9 +332,9 @@ public function __get($resourceName) */ public function __call($resourceName, $arguments) { - if (!in_array($resourceName, $this->resources)) { + if (!in_array($resourceName, self::$resources)) { if (isset($this->childResources[$resourceName])) { - $message = "$resourceName is a child resource of " . $this->childResources[$resourceName] . ". Cannot be accessed directly."; + $message = "$resourceName is a child resource of " . self::$childResources[$resourceName] . ". Cannot be accessed directly."; } else { $message = "Invalid resource name $resourceName. Pls check the API Reference to get the appropriate resource name."; } @@ -334,73 +347,55 @@ public function __call($resourceName, $arguments) $resourceID = !empty($arguments) ? $arguments[0] : null; //Initiate the resource object - $resource = new $resourceClassName($resourceID); + return new $resourceClassName($this->config, $resourceID); + } - return $resource; + /** + * Return new SDK Client + * @deprecated use: new ShopifySDK($config) + * @param array $config + */ + public static function config($config) + { + return new self($config); } /** * Configure the SDK client * * @param array $config - * - * @return ShopifySDK */ - public static function config($config) + public static function buildConfig($config) { /** * Reset config to it's initial values */ - self::$config = array( - 'ApiVersion' => self::$defaultApiVersion + $config = array_merge( + array('ApiVersion' => self::$defaultApiVersion), + $config ); - foreach ($config as $key => $value) { - self::$config[$key] = $value; - } - //Re-set the admin url if shop url is changed if(isset($config['ShopUrl'])) { - self::setAdminUrl(); - } - - //If want to keep more wait time than .5 seconds for each call - if (isset($config['AllowedTimePerCall'])) { - static::$timeAllowedForEachApiCall = $config['AllowedTimePerCall']; - } - - if (isset($config['Curl']) && is_array($config['Curl'])) { - CurlRequest::config($config['Curl']); - } + $shopUrl = $config['ShopUrl']; - return new ShopifySDK; - } + //Remove https:// and trailing slash (if provided) + $shopUrl = preg_replace('#^https?://|/$#', '', $shopUrl); + $apiVersion = $config['ApiVersion']; - /** - * Set the admin url, based on the configured shop url - * - * @return string - */ - public static function setAdminUrl() - { - $shopUrl = self::$config['ShopUrl']; - - //Remove https:// and trailing slash (if provided) - $shopUrl = preg_replace('#^https?://|/$#', '', $shopUrl); - $apiVersion = self::$config['ApiVersion']; + if(isset($config['ApiKey']) && isset($config['Password'])) { + $apiKey = $config['ApiKey']; + $apiPassword = $config['Password']; + $adminUrl = "https://$apiKey:$apiPassword@$shopUrl/admin/"; + } else { + $adminUrl = "https://$shopUrl/admin/"; + } - if(isset(self::$config['ApiKey']) && isset(self::$config['Password'])) { - $apiKey = self::$config['ApiKey']; - $apiPassword = self::$config['Password']; - $adminUrl = "https://$apiKey:$apiPassword@$shopUrl/admin/"; - } else { - $adminUrl = "https://$shopUrl/admin/"; + $config['AdminUrl'] = $adminUrl; + $config['ApiUrl'] = $adminUrl . "api/$apiVersion/"; } - self::$config['AdminUrl'] = $adminUrl; - self::$config['ApiUrl'] = $adminUrl . "api/$apiVersion/"; - - return $adminUrl; + return $config; } /** @@ -408,8 +403,8 @@ public static function setAdminUrl() * * @return string */ - public static function getAdminUrl() { - return self::$config['AdminUrl']; + public function getAdminUrl() { + return $this->config['AdminUrl']; } /** @@ -417,8 +412,8 @@ public static function getAdminUrl() { * * @return string */ - public static function getApiUrl() { - return self::$config['ApiUrl']; + public function getApiUrl() { + return $this->config['ApiUrl']; } /** @@ -450,19 +445,19 @@ public static function getEmbeddedAppUrl($host) * * @param bool $firstCallWait Whether to maintain the wait time even if it is the first API call */ - public static function checkApiCallLimit($firstCallWait = false) + public function checkApiCallLimit($firstCallWait = false) { $timeToWait = 0; - if (static::$microtimeOfLastApiCall == null) { + if ($this->microtimeOfLastApiCall == null) { if ($firstCallWait) { - $timeToWait = static::$timeAllowedForEachApiCall; + $timeToWait = $this->timeAllowedForEachApiCall; } } else { $now = microtime(true); - $timeSinceLastCall = $now - static::$microtimeOfLastApiCall; + $timeSinceLastCall = $now - $this->microtimeOfLastApiCall; //Ensure 2 API calls per second - if($timeSinceLastCall < static::$timeAllowedForEachApiCall) { - $timeToWait = static::$timeAllowedForEachApiCall - $timeSinceLastCall; + if($timeSinceLastCall < $this->timeAllowedForEachApiCall) { + $timeToWait = $this->timeAllowedForEachApiCall - $timeSinceLastCall; } } @@ -473,6 +468,6 @@ public static function checkApiCallLimit($firstCallWait = false) usleep($microSecondsToWait); } - static::$microtimeOfLastApiCall = microtime(true); + $this->microtimeOfLastApiCall = microtime(true); } } diff --git a/tests/TestResource.php b/tests/TestResource.php index 3fd3b2d..1f0cdf6 100644 --- a/tests/TestResource.php +++ b/tests/TestResource.php @@ -26,8 +26,8 @@ public static function setUpBeforeClass() 'AccessToken' => getenv('SHOPIFY_API_PASSWORD'), //Your Access Token(Private API Password) ); - self::$shopify = ShopifySDK::config($config); - ShopifySDK::checkApiCallLimit(); + self::$shopify = new ShopifySDK($config); + self::$shopify->checkApiCallLimit(); } /** @@ -37,4 +37,4 @@ public static function tearDownAfterClass() { self::$shopify = null; } -} \ No newline at end of file +}