Request::handleRateLimit()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 9.6666
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 0
crap 3
1
<?php
2
3
namespace ShopifyClient;
4
5
use GuzzleHttp\ClientInterface;
6
use GuzzleHttp\Exception\ClientException;
7
use ShopifyClient\Exception\ShopifyException;
8
9
class Request
10
{
11
    const API_CALL_LIMIT_HEADER = 'http_x_shopify_shop_api_call_limit';
12
    const METHOD_POST = 'POST';
13
    const METHOD_GET = 'GET';
14
    const METHOD_PUT = 'PUT';
15
    const METHOD_DELETE = 'DELETE';
16
17
    /**
18
     * @var ClientInterface
19
     */
20
    protected $httpClient;
21
22
    /**
23
     * @var string
24
     */
25
    protected $responseKey;
26
27
    /**
28
     * @var int
29
     */
30
    private $rateLimit;
31
32
    /**
33
     * @var int
34
     */
35
    private $callsMade;
36
37
    /**
38
     * @var float
39
     */
40
    private static $callCycle = 0.5; // avg. 2 calls a second
41
42
    /**
43
     * @var float
44
     */
45
    private $rateLimitThreshold = 0.8;
46
47
    /**
48
     * @var int
49
     */
50
    private $rateLimitReached = 0;
51
52
    /**
53
     * Request constructor.
54
     * @param ClientInterface $client
55
     */
56 5
    public function __construct(ClientInterface $client)
57
    {
58 5
        $this->httpClient = $client;
59 5
    }
60
61
    /**
62
     * @param string $method
63
     * @param string $endpoint
64
     * @param array $parameters
65
     * @return bool
66
     * @throws ShopifyException
67
     */
68 130
    public function request(string $method, string $endpoint, array $parameters = [])
69
    {
70 130
        $this->handleRateLimit();
71
72
        try {
73 130
            $response = $this->httpClient->request($method, $endpoint, $parameters);
74 3
        } catch (ClientException $e) {
75 3
            $response = $e->getResponse();
76 3
            $content  = json_decode($response->getBody()->getContents(), true);
77
78 3
            throw new ShopifyException($content['errors'], $response->getStatusCode(), $e);
79
        }
80
81 127
        $this->setRateLimit($response->getHeader(self::API_CALL_LIMIT_HEADER));
82
83 127
        $data = json_decode($response->getBody()->getContents(), true);
84
85 127
        if (strlen($this->responseKey) > 0) {
86 105
            return $data[$this->responseKey];
87
        } else {
88 23
            return true;
89
        }
90
    }
91
92
    /**
93
     * @param string $responseKey
94
     * @return Request
95
     */
96 130
    public function setResponseKey($responseKey)
97
    {
98 130
        $this->responseKey = $responseKey;
99 130
    }
100
101
    /**
102
     * @param callable $function
103
     * @return mixed
104
     */
105 16
    public static function throttle(callable $function)
106
    {
107 16
        $start    = time();
108 16
        $result   = $function();
109 16
        $end      = time();
110 16
        $duration = $end - $start;
111 16
        $waitTime = ceil(self::$callCycle - $duration);
112
113 16
        if ($waitTime > 0) {
114 12
            sleep($waitTime);
115
        }
116
117 16
        return $result;
118
    }
119
120
    /**
121
     * @return int
122
     */
123 1
    public function getRateLimitReached(): int
124
    {
125 1
        return $this->rateLimitReached;
126
    }
127
128 130
    private function handleRateLimit()
129
    {
130 130
        if ($this->callsMade > 0 && $this->isRateLimitReached()) {
131 3
            $this->rateLimitReached++;
132
            // Prevent bucket overflow
133
            // https://help.shopify.com/api/getting-started/api-call-limit
134 3
            usleep(rand(3, 10) * 1000000);
135
        }
136 130
    }
137
138
    /**
139
     * @param array $header
140
     */
141 127
    private function setRateLimit(array $header)
142
    {
143 127
        if (empty($header)) {
144 2
            return;
145
        }
146
147 125
        $parts           = explode('/', $header[0]);
148 125
        $this->rateLimit = $parts[1];
149 125
        $this->callsMade = $parts[0];
150 125
    }
151
152
    /**
153
     * @return bool
154
     */
155 121
    public function isRateLimitReached(): bool
156
    {
157 121
        return $this->getCallLimit() >= $this->rateLimitThreshold;
158
    }
159
160
    /**
161
     * @return float|int
162
     */
163 121
    public function getCallLimit()
164
    {
165 121
        return $this->callsMade / $this->rateLimit;
166
    }
167
}
168