Completed
Push — master ( ae1643...7339ae )
by Taosikai
12:51
created

Client::initializeBaseServices()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
/*
4
 * This file is part of the slince/shopify-api-php
5
 *
6
 * (c) Slince <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Slince\Shopify;
13
14
use Doctrine\Common\Inflector\Inflector;
15
use GuzzleHttp\Exception\GuzzleException;
16
use GuzzleHttp\Psr7\Request;
17
use Slince\Di\Container;
18
use GuzzleHttp\Client as HttpClient;
19
use Psr\Http\Message\RequestInterface;
20
use Psr\Http\Message\ResponseInterface;
21
use Slince\Shopify\Common\Manager\ManagerInterface;
22
use Slince\Shopify\Exception\InvalidArgumentException;
23
use GuzzleHttp\Exception\RequestException;
24
use Slince\Shopify\Exception\ClientException;
25
use Slince\Shopify\Exception\RuntimeException;
26
use Slince\Shopify\Hydrator\Hydrator;
27
28
/**
29
 * @method Manager\Article\ArticleManagerInterface getArticleManager
30
 * @method Manager\Asset\AssetManagerInterface getAssetManager
31
 * @method Manager\Blog\BlogManagerInterface getBlogManager
32
 * @method Manager\CarrierService\CarrierServiceManagerInterface getCarrierServiceManager
33
 * @method Manager\Collect\CollectManagerInterface getCollectManager
34
 * @method Manager\Comment\CommentManagerInterface getCommentManager
35
 * @method Manager\Country\CountryManagerInterface getCountryManager
36
 * @method Manager\CustomCollection\CustomCollectionManagerInterface getCustomCollectionManager
37
 * @method Manager\Customer\CustomerManagerInterface getCustomerManager
38
 * @method Manager\CustomerAddress\AddressManagerInterface getCustomerAddressManager
39
 * @method Manager\CustomerSavedSearch\CustomerSavedSearchManagerInterface getCustomerSavedSearchManager
40
 * @method Manager\DiscountCode\DiscountCodeManagerInterface getDiscountCodeManager
41
 * @method Manager\Fulfillment\FulfillmentManagerInterface getFulfillmentManager
42
 * @method Manager\FulfillmentService\FulfillmentServiceManagerInterface getFulfillmentServiceManager
43
 * @method Manager\InventoryItem\InventoryItemManagerInterface getInventoryItemManager
44
 * @method Manager\InventoryLevel\InventoryLevelManagerInterface getInventoryLevelManager
45
 * @method Manager\Location\LocationManagerInterface getLocationManager
46
 * @method Manager\Order\OrderManagerInterface getOrderManager
47
 * @method Manager\OrderRisk\RiskManagerInterface getOrderRiskManager
48
 * @method Manager\Page\PageManagerInterface getPageManager
49
 * @method Manager\Policy\PolicyManagerInterface getPolicyManager
50
 * @method Manager\PriceRule\PriceRuleManagerInterface getPriceRuleManager
51
 * @method Manager\Product\ProductManagerInterface getProductManager
52
 * @method Manager\ProductImage\ImageManagerInterface getProductImageManager
53
 * @method Manager\ProductVariant\VariantManagerInterface getProductVariantManager
54
 * @method Manager\Province\ProvinceManagerInterface getProvinceManager
55
 * @method Manager\RecurringApplicationCharge\RecurringApplicationChargeManagerInterface getRecurringApplicationChargeManager
56
 * @method Manager\Redirect\RedirectManagerInterface getRedirectManager
57
 * @method Manager\Refund\RefundManagerInterface getRefundManager
58
 * @method Manager\ShippingZone\ShippingZoneManagerInterface getShippingZoneManager
59
 * @method Manager\Shop\ShopManagerInterface getShopManager
60
 * @method Manager\Theme\ThemeManagerInterface getThemeManager
61
 * @method Manager\Transaction\TransactionManagerInterface getTransactionManager
62
 * @method Manager\Webhook\WebhookManagerInterface getWebhookManager
63
 */
64
class Client
65
{
66
    const NAME = 'SlinceShopifyClient';
67
68
    const VERSION = '2.1.0';
69
70
    /**
71
     * @var HttpClient
72
     */
73
    protected $httpClient;
74
75
    /**
76
     * @var Container
77
     */
78
    protected $container;
79
80
    /**
81
     * @var CredentialInterface
82
     */
83
    protected $credential;
84
85
    /**
86
     * The shop.
87
     *
88
     * @var string
89
     */
90
    protected $shop;
91
92
    /**
93
     * Array of services classes.
94
     *
95
     * @var array
96
     */
97
    public $serviceClass = [
98
        Manager\Article\ArticleManager::class,
99
        Manager\Asset\AssetManager::class,
100
        Manager\Blog\BlogManager::class,
101
        Manager\CarrierService\CarrierServiceManager::class,
102
        Manager\Collect\CollectManager::class,
103
        Manager\Comment\CommentManager::class,
104
        Manager\Country\CountryManager::class,
105
        Manager\CustomCollection\CustomCollectionManager::class,
106
        Manager\Customer\CustomerManager::class,
107
        Manager\CustomerAddress\AddressManager::class,
108
        Manager\CustomerSavedSearch\CustomerSavedSearchManager::class,
109
        Manager\DiscountCode\DiscountCodeManager::class,
110
        Manager\Fulfillment\FulfillmentManager::class,
111
        Manager\FulfillmentService\FulfillmentServiceManager::class,
112
        Manager\InventoryItem\InventoryItemManager::class,
113
        Manager\InventoryLevel\InventoryLevelManager::class,
114
        Manager\Location\LocationManager::class,
115
        Manager\Order\OrderManager::class,
116
        Manager\OrderRisk\RiskManager::class,
117
        Manager\Page\PageManager::class,
118
        Manager\Policy\PolicyManager::class,
119
        Manager\PriceRule\PriceRuleManager::class,
120
        Manager\Product\ProductManager::class,
121
        Manager\ProductImage\ImageManager::class,
122
        Manager\ProductVariant\VariantManager::class,
123
        Manager\Province\ProvinceManager::class,
124
        Manager\RecurringApplicationCharge\RecurringApplicationChargeManager::class,
125
        Manager\Redirect\RedirectManager::class,
126
        Manager\Refund\RefundManager::class,
127
        Manager\ScriptTag\ScriptTagManager::class,
128
        Manager\ShippingZone\ShippingZoneManager::class,
129
        Manager\Shop\ShopManager::class,
130
        Manager\Theme\ThemeManager::class,
131
        Manager\Transaction\TransactionManager::class,
132
        Manager\Webhook\WebhookManager::class,
133
    ];
134
135
    protected $metaDirs = [
136
        'Slince\Shopify' => __DIR__.'/../config/serializer'
137
    ];
138
139
    /**
140
     * Whether delay the next request.
141
     *
142
     * @var bool
143
     */
144
    protected static $delayNextRequest = false;
145
146
    /**
147
     * @var string
148
     */
149
    protected $metaCacheDir;
150
151
    /**
152
     * @var Hydrator
153
     */
154
    protected $hydrator;
155
156
    public function __construct(CredentialInterface $credential, $shop, array $options = [])
157
    {
158
        $this->container = new Container();
159
        $this->container->register($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Slince\Shopify\Client>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
160
        $this->credential = $credential;
161
        $this->setShop($shop);
162
        $this->applyOptions($options);
163
        $this->initializeBaseServices();
164
    }
165
166
    public function __call($name, $arguments)
167
    {
168
        if ('Manager' === substr($name, -7)) {
169
            $serviceName = substr($name, 3, -7);
170
171
            return $this->container->get(Inflector::tableize(Inflector::pluralize($serviceName)));
172
        }
173
        throw new \InvalidArgumentException(sprintf('The method "%s" is not exists', $name));
174
    }
175
176
    /**
177
     * Gets the credential.
178
     *
179
     * @return CredentialInterface
180
     */
181
    public function getCredential()
182
    {
183
        return $this->credential;
184
    }
185
186
    /**
187
     * sets the shop name for the client.
188
     *
189
     * @param string $shop
190
     */
191
    public function setShop($shop)
192
    {
193
        if (!preg_match('/^[a-zA-Z0-9\-]{3,100}\.myshopify\.(?:com|io)$/', $shop)) {
194
            throw new InvalidArgumentException(
195
                'Shop name should be 3-100 letters, numbers, or hyphens e.g. your-store.myshopify.com'
196
            );
197
        }
198
        $this->shop = $shop;
199
    }
200
201
    /**
202
     * Gets the shop.
203
     *
204
     * @return string
205
     */
206
    public function getShop()
207
    {
208
        return $this->shop;
209
    }
210
211
    /**
212
     * Sets the http client for the client.
213
     *
214
     * @param HttpClient $httpClient
215
     */
216
    public function setHttpClient($httpClient)
217
    {
218
        $this->httpClient = $httpClient;
219
    }
220
221
    /**
222
     * Gets the http client.
223
     *
224
     * @return HttpClient
225
     */
226
    public function getHttpClient()
227
    {
228
        if ($this->httpClient) {
229
            return $this->httpClient;
230
        }
231
        return $this->httpClient = new HttpClient([
232
            'verify' => false,
233
        ]);
234
    }
235
236
    /**
237
     * Perform a GET request.
238
     *
239
     * @param string $resource
240
     * @param array  $query
241
     *
242
     * @return array
243
     */
244
    public function get($resource, $query = [])
245
    {
246
        return $this->doRequest('GET', $resource, [
247
            'query' => $query,
248
        ]);
249
    }
250
251
    /**
252
     * Perform a POST request.
253
     *
254
     * @param string $resource
255
     * @param array  $data
256
     * @param array  $query
257
     *
258
     * @return array
259
     */
260
    public function post($resource, $data, $query = [])
261
    {
262
        return $this->doRequest('POST', $resource, [
263
            'query' => $query,
264
            'json' => $data,
265
        ]);
266
    }
267
268
    /**
269
     * Perform a PUT request.
270
     *
271
     * @param string $resource
272
     * @param array  $data
273
     * @param array  $query
274
     *
275
     * @return array
276
     */
277
    public function put($resource, $data, $query = [])
278
    {
279
        return $this->doRequest('PUT', $resource, [
280
            'query' => $query,
281
            'json' => $data,
282
        ]);
283
    }
284
285
    /**
286
     * Perform a DELETE request.
287
     *
288
     * @param string $resource
289
     * @param array  $query
290
     */
291
    public function delete($resource, $query = [])
292
    {
293
        $this->doRequest('DELETE', $resource, [
294
            'query' => $query
295
        ]);
296
    }
297
298
    protected function doRequest($method, $resource, $options = [])
299
    {
300
        $request = new Request($method, $this->buildUrl($resource), [
301
            'User-Agent' => static::NAME . '/' . static::VERSION
302
        ]);
303
        $response = $this->sendRequest($request, $options);
304
        $body = $response->getBody();
305
306
        return $body->getSize()
307
            ? \GuzzleHttp\json_decode($body, true)
308
            : [];
309
    }
310
311
    /**
312
     * Builds an url by given resource name.
313
     *
314
     * @param string $resource
315
     *
316
     * @return string
317
     */
318
    protected function buildUrl($resource)
319
    {
320
        return sprintf('https://%s/admin/%s.json', $this->shop, $resource);
321
    }
322
323
    /**
324
     * Send a request.
325
     *
326
     * @param RequestInterface $request
327
     * @param array $options
328
     *
329
     * @return ResponseInterface
330
     * @throws GuzzleException
331
     * @codeCoverageIgnore
332
     */
333
    public function sendRequest(RequestInterface $request, array $options = [])
334
    {
335
        $request = $request->withHeader('Content-Type', 'application/json');
336
        $request = $this->credential->applyToRequest($request);
337
        if (static::$delayNextRequest) {
338
            usleep(1000000 * rand(3, 10));
339
        }
340
        try {
341
            $response = $this->getHttpClient()->send($request, $options);
342
        } catch (RequestException $exception) {
343
            $exception = new ClientException($request, $exception->getResponse(), $exception->getMessage());
344
            throw $exception;
345
        }
346
        list($callsMade, $callsLimit) = explode('/', $response->getHeaderLine('http_x_shopify_shop_api_call_limit'));
347
        static::$delayNextRequest = $callsMade / $callsLimit >= 0.8;
348
349
        return $response;
350
    }
351
352
    /**
353
     * Applies the array of request options to the client.
354
     *
355
     * @param array $options
356
     */
357
    protected function applyOptions(array $options)
358
    {
359
        isset($options['httpClient']) && $this->httpClient = $options['httpClient'];
360
        if (!isset($options['metaCacheDir'])) {
361
            throw new InvalidArgumentException('You must provide option "metaCacheDir"');
362
        }
363
        $this->metaCacheDir = $options['metaCacheDir'];
364
    }
365
366
    /**
367
     * Gets the hydrator instance.
368
     *
369
     * @return Hydrator
370
     */
371
    public function getHydrator()
372
    {
373
        if ($this->hydrator) {
374
            return $this->hydrator;
375
        }
376
        return $this->hydrator = new Hydrator($this->metaCacheDir, $this->metaDirs);
377
    }
378
379
    /**
380
     * Add a custom meta dir.
381
     *
382
     * @param string $namespace
383
     * @param string $path
384
     * @throws RuntimeException
385
     */
386
    public function addMetaDir($namespace, $path)
387
    {
388
        if ($this->hydrator) {
389
            throw new RuntimeException(sprintf('The hydrator has been built, you should add meta dir before getting manager.'));
390
        }
391
        $this->metaDirs[$namespace] = $path;
392
    }
393
394
    /**
395
     * Add a custom service class.
396
     *
397
     * @param string $serviceClass
398
     * @throws InvalidArgumentException
399
     */
400
    public function addServiceClass($serviceClass)
401
    {
402
        if (!is_subclass_of($serviceClass, ManagerInterface::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Slince\Shopify\Common\M...ManagerInterface::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
403
            throw new InvalidArgumentException(sprintf('The service class "%s" should implement "ManagerInterface"', $serviceClass));
404
        }
405
        $this->serviceClass[] = $serviceClass;
406
        $this->container->register($serviceClass::getServiceName(), $serviceClass);
407
    }
408
409
    /**
410
     * Initialize base services.
411
     */
412
    protected function initializeBaseServices()
413
    {
414
        foreach ($this->serviceClass as $serviceClass) {
415
            $this->container->register($serviceClass::getServiceName(), $serviceClass);
416
        }
417
    }
418
}
419