11<?php
22namespace Shopify ;
33
4+ use GuzzleHttp \Exception \RequestException ;
45use Shopify \Common \ClientInterface ;
56use Shopify \Exception \ApiException ;
67
78/**
89 * Class Client
10+ * @package Shopify
911 */
1012class Client implements ClientInterface
1113{
@@ -24,97 +26,109 @@ class Client implements ClientInterface
2426 */
2527 const SHOPIFY_ACCESS_TOKEN = 'X-Shopify-Access-Token ' ;
2628
29+ /**
30+ * denine response header pagination string
31+ */
32+ const PAGINATION_STRING = 'Link ' ;
33+
2734 /**
2835 * response header parameter of shopify api limit
2936 */
30- const SHOPIFY_API_LIMIT_HEADER = 'http_x_shopify_shop_api_call_limit ' ;
37+ const API_CALL_RATE_LIMIT_HEADER = 'http_x_shopify_shop_api_call_limit ' ;
3138
3239 /**
3340 * define graphQL api call
3441 */
3542 const GRAPHQL = 'graphql ' ;
3643
3744 /**
38- * @var string
3945 * Shopify graphql base url
46+ * @var string
4047 */
4148 protected $ graphql_api_url = "https://{shopify_domain}/admin/api/{version}/graphql.json " ;
4249
4350 /**
44- * @var string
4551 * Shopify domain name
52+ * @var string
4653 */
4754 protected $ shop ;
4855
4956 /**
50- * @var string
5157 * Shopify api key
58+ * @var string
5259 */
5360 protected $ api_key ;
5461
5562 /**
56- * @var string
5763 * Shopify password for private app
64+ * @var string
5865 */
5966 protected $ password ;
6067
6168 /**
62- * @var string
6369 * Shopify shared secret key for private app
70+ * @var string
6471 */
6572 protected $ shared_secret ;
6673
6774 /**
75+ * array('version')
6876 * @var array
69- * array of version
7077 */
7178 protected $ api_params ;
7279
7380 /**
74- * @var array
7581 * Shopify api call url
82+ * @var array
7683 */
7784 protected $ base_urls ;
7885
7986 /**
80- * @var array
8187 * get api header array according to private and public app
88+ * @var array
8289 */
8390 protected $ requestHeaders ;
8491
8592 /**
86- * @var string
8793 * Shopify api version
94+ * @var string
8895 */
8996 protected $ api_version ;
9097
9198 /**
99+ * get response header
92100 * @var string
101+ */
102+ protected $ next_page ;
103+
104+ /**
93105 * get response header
106+ * @var string
94107 */
95- private $ last_response_headers ;
108+ protected $ prev_page ;
96109
97110 /**
98- *
111+ * static variable to api is going to reach
112+ * @var bool
99113 */
100- public function init (){
101- echo "hello " ;
102- }
114+ protected static $ wait_next_api_call = false ;
103115
104116 /**
117+ * prepare data for rest api request
105118 * @param $method
106- * @param $query
119+ * @param $path
107120 * @param array $params
108- * @return mixed|void
121+ * @return array
109122 * @throws ApiException
123+ * @throws ClientException
110124 */
111125 public function call ($ method , $ path , array $ params = [])
112126 {
113127 $ url = $ this ->base_urls [self ::REST_API ];
114128 $ options = [];
115129 $ allowed_http_methods = $ this ->getHttpMethods ();
116130 if (!in_array ($ method , $ allowed_http_methods )){
117- throw new ApiException (implode (", " ,$ allowed_http_methods )." http methods are allowed. " );
131+ throw new ApiException (implode (", " ,$ allowed_http_methods )." http methods are allowed. " , 0 );
118132 }
119133 if (isset ($ this ->requestHeaders [self ::REST_API ]) && is_array ($ this ->requestHeaders [self ::REST_API ])) {
120134 $ options ['headers ' ] = $ this ->requestHeaders [self ::REST_API ];
@@ -130,74 +144,176 @@ public function call($method, $path , array $params = [])
130144 return $ this ->request ($ method ,$ url ,$ options );
131145 }
132146
133- public function callGraphql ($ method , $ query )
147+ /**
148+ * prepare data for graphql api request
149+ * @param string $query
150+ * @return mixed|void
151+ * @throws ApiException
152+ * @throws ClientException
153+ */
154+ public function callGraphql ($ query )
134155 {
135156 $ url = $ this ->base_urls [self ::GRAPHQL ];
136157 $ options = [];
137- $ allowed_http_methods = $ this ->getHttpMethods ();
138- if (!in_array ($ method , $ allowed_http_methods ))
139- {
140- throw new ApiException (implode (", " ,$ allowed_http_methods )." http methods are allowed. " );
141- }
142158 if (isset ($ this ->requestHeaders [self ::GRAPHQL ]) && is_array ($ this ->requestHeaders [self ::GRAPHQL ])) {
143159 $ options ['headers ' ] = $ this ->requestHeaders [self ::GRAPHQL ];
144160 }
145- $ options ['body ' ] = json_encode ([
146- 'query ' => $ query ,
147- ]);
148- // var_dump($options);die;
149- return $ this ->request ($ method ,$ url ,$ options );
161+
162+ $ options ['body ' ] = $ query ;
163+ if (self ::$ wait_next_api_call )
164+ {
165+ usleep (1000000 * rand (3 , 6 ));
166+ }
167+ $ response = $ this ->request ('POST ' , $ url , $ options );
168+ if (isset ($ response ['errors ' ]))
169+ {
170+ $ http_bad_request_code = 400 ;
171+ throw new ApiException (\GuzzleHttp \json_encode ($ response ['errors ' ]),$ http_bad_request_code );
172+ }
150173 }
151174
175+ /**
176+ * send http request
177+ * @param string $method
178+ * @param string $url
179+ * @param array $options
180+ * @return array|mixed
181+ * @throws ApiException
182+ */
152183 public function request ($ method ,$ url ,array $ options )
153184 {
154- $ client = new \GuzzleHttp \Client ();
155- $ http_response = $ client ->request ($ method , $ url , $ options );
156- echo "<pre> " ;
157- print_r ($ http_response ->getBody ()->getContents ());
158- print_r ($ http_response ->getHeader (self ::SHOPIFY_API_LIMIT_HEADER ));
159- print_r ($ http_response ->getStatusCode ());
160- print_r ($ http_response ->getHeaders ());
161- die;
185+ try
186+ {
187+ $ client = new \GuzzleHttp \Client ();
188+ $ http_response = $ client ->request ($ method , $ url , $ options );
189+ $ response_content = $ http_response ->getBody ()->getContents ();
190+ $ response = [];
191+ if (strtoupper ($ method ) === 'GET ' && $ http_response ->getHeaderLine (self ::PAGINATION_STRING )) {
192+ $ this ->next_page = $ this ->parseLinkString ($ http_response ->getHeaderLine (self ::PAGINATION_STRING ),'next ' );
193+ $ this ->prev_page = $ this ->parseLinkString ($ http_response ->getHeaderLine (self ::PAGINATION_STRING ),'previous ' );
194+ }
195+
196+ $ response = \GuzzleHttp \json_decode ($ response_content ,true );
197+ if ($ http_response ->getHeaderLine (self ::API_CALL_RATE_LIMIT_HEADER )) {
198+ list ($ api_call_requested , $ api_call_Limit ) = explode ('/ ' , $ http_response ->getHeaderLine (self ::API_CALL_RATE_LIMIT_HEADER ));
199+ static ::$ wait_next_api_call = $ api_call_requested / $ api_call_Limit >= 0.8 ;
200+ }
201+ }
202+ catch (RequestException $ e )
203+ {
204+ $ json_error = json_decode ($ e ->getResponse ()->getBody ()->getContents (),true );
205+ if (isset ($ json_error ['errors ' ])) {
206+ $ error_message = $ json_error ['errors ' ];
207+ }
208+ else {
209+ $ error_message = $ e ->getResponse ()->getBody ()->getContents ();
210+ }
211+ throw new ApiException ($ error_message ,$ e ->getCode ());
212+ }
213+ return $ response ;
162214 }
163215
216+ /**
217+ * get previous page_info for any resource(products/orders)
218+ * @return string
219+ */
220+ public function getPrevPage ()
221+ {
222+ return $ this ->prev_page ;
223+ }
164224
165225 /**
226+ * check previous page_info for any resource(products/orders)
227+ * @return string
228+ */
229+ public function hasPrevPage ()
230+ {
231+ return !empty ($ this ->prev_page );
232+ }
233+
234+ /**
235+ * get next page_info for any resource(products/orders)
236+ * @return string
237+ */
238+ public function getNextPage (){
239+ return $ this ->next_page ;
240+ }
241+
242+ /**
243+ * check next page_info for any resource(products/orders)
244+ * @return string
245+ */
246+ public function hasNextPage (){
247+ return !empty ($ this ->next_page );
248+ }
249+
250+ /**
251+ * parse header string for previous and next page_info
252+ * @param $pagination_string
253+ * @param $page_link
254+ * @return string
255+ */
256+ public function parseLinkString ($ pagination_string ,$ page_link )
257+ {
258+ $ matches = [];
259+ preg_match ("/<(.*page_info=([a-z0-9\-]+).*)>; rel= \"? {$ page_link }\"?/i " , $ pagination_string , $ matches );
260+ return isset ($ matches [2 ]) ? $ matches [2 ] : NULL ;
261+ }
262+
263+
264+ /**
265+ * return latest api version
166266 * @return string
167- * @throws ApiException
168267 */
169268 public function getApiVersion ()
170269 {
171270 return $ this ->api_version ;
172271 }
173272
273+ /**
274+ * set api version
275+ * @param api_version
276+ * Exception for valid value
277+ * @throws ApiException
278+ */
174279 protected function setApiVersion ()
175280 {
176281 $ this ->api_version = !empty ($ this ->api_params ['version ' ])?$ this ->api_params ['version ' ]:self ::SHOPIFY_API_VERSION ;
177282 if (!preg_match ('/^[0-9]{4}-[0-9]{2}$|^unstable$/ ' , $ this ->api_version ))
178283 {
179- throw new ApiException ('Api Version must be of YYYY-MM or unstable ' );
284+ throw new ApiException ('Api Version must be of YYYY-MM or unstable ' , 0 );
180285 }
181286 }
182287
183288 /**
289+ * return allowed http api methods
184290 * @return array
185291 */
186292 public function getHttpMethods ()
187293 {
188294 return ['POST ' , 'PUT ' ,'GET ' , 'DELETE ' ];
189295 }
190296
297+ /**
298+ * set shopify domain
299+ * @param $shop
300+ * Exception for invalid shop name
301+ * @throws ApiException
302+ */
191303 protected function setShop ($ shop )
192304 {
193305 if (!preg_match ('/^[a-zA-Z0-9\-]{3,100}\.myshopify\.(?:com|io)$/ ' , $ shop )) {
194306 throw new ApiException (
195- 'Shop name should be 3-100 letters, numbers, or hyphens eg mypetstore.myshopify.com '
307+ 'Shop name should be 3-100 letters, numbers, or hyphens eg mypetstore.myshopify.com ' , 0
196308 );
197309 }
198310 $ this ->shop = $ shop ;
199311 }
200312
313+ /**
314+ * return shopify domain
315+ * @return string
316+ */
201317 public function getShop ()
202318 {
203319 return $ this ->shop ;
0 commit comments