Completed
Push — develop ( 02e6e4...770dc9 )
by Edwin
14:52
created

AbstractResource   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 133
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 12
lcom 2
cbo 5
dl 0
loc 133
ccs 0
cts 37
cp 0
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B request() 0 25 5
A throttle() 0 17 2
A setRateLimit() 0 7 1
A isRateLimitReached() 0 4 1
A getCallLimit() 0 4 1
A isCountable() 0 4 1
1
<?php
2
3
namespace ShopifyClient\Resource;
4
5
use GuzzleHttp\Client;
6
use GuzzleHttp\Exception\RequestException;
7
use ShopifyClient\Exception\ClientException;
8
9
abstract class AbstractResource
10
{
11
    const API_CALL_LIMIT_HEADER = 'http_x_shopify_shop_api_call_limit';
12
13
    /**
14
     * @var Client
15
     */
16
    protected $httpClient;
17
18
    /**
19
     * @var int
20
     */
21
    private $rateLimit;
22
23
    /**
24
     * @var int
25
     */
26
    private $callsMade;
27
28
    /**
29
     * @var float
30
     */
31
    private $callCycle = 0.5; // avg. 2 calls a second
32
33
    /**
34
     * @var float
35
     */
36
    private $rateLimitThreshold = 0.8;
37
38
    /**
39
     * @var bool
40
     */
41
    protected $countable = false;
42
43
    /**
44
     * AbstractResource constructor.
45
     * @param Client $client
46
     */
47
    public function __construct(Client $client)
48
    {
49
        $this->httpClient = $client;
50
    }
51
52
    /**
53
     * @param $method
54
     * @param $endpoint
55
     * @param array $params
56
     * @return array
57
     * @throws ClientException
58
     */
59
    public function request($method, $endpoint, $params = [])
60
    {
61
        if ($method !== 'GET') {
62
            $params['headers']['Content-Type'] = 'application/json';
63
        }
64
65
        if ($this->callsMade > 0 && $this->isRateLimitReached()) {
66
            // Prevent bucket overflow
67
            // https://help.shopify.com/api/getting-started/api-call-limit
68
            usleep(rand(3, 10) * 1000000);
69
        }
70
71
        try {
72
            $response = $this->httpClient->request($method, $endpoint, $params);
73
        } catch (RequestException $e) {
74
            $response = $e->getResponse();
75
            $content  = json_decode($response->getBody()->getContents(), true);
76
77
            throw new ClientException($content['errors'], $response->getStatusCode());
78
        }
79
80
        $this->setRateLimit($response->getHeader(self::API_CALL_LIMIT_HEADER));
81
82
        return json_decode($response->getBody()->getContents(), true);
83
    }
84
85
    /**
86
     * With a "leak rate" of 2 calls per second that continually empties the bucket.
87
     * If your app averages 2 calls per second, it will never trip a 429 error ("bucket overflow").
88
     *
89
     * @param callable $function
90
     * @return mixed
91
     */
92
    public function throttle(callable $function)
93
    {
94
        $start = time();
95
96
        $result = $function();
97
98
        $end = time();
99
100
        $duration = $end - $start;
101
        $waitTime = ceil($this->callCycle - $duration);
102
103
        if ($waitTime > 0) {
104
            sleep($waitTime);
105
        }
106
107
        return $result;
108
    }
109
110
    /**
111
     * @param array $header
112
     */
113
    private function setRateLimit(array $header)
114
    {
115
        $parts = explode('/', $header[0]);
116
117
        $this->rateLimit = $parts[1];
118
        $this->callsMade = $parts[0];
119
    }
120
121
    /**
122
     * @return bool
123
     */
124
    public function isRateLimitReached(): bool
125
    {
126
        return $this->getCallLimit() >= $this->rateLimitThreshold;
127
    }
128
129
    public function getCallLimit()
130
    {
131
        return $this->callsMade / $this->rateLimit;
132
    }
133
134
    /**
135
     * @return bool
136
     */
137
    public function isCountable(): bool
138
    {
139
        return $this->countable;
140
    }
141
}
142