Passed
Push — develop ( 3e77ee...386e9a )
by Edwin
02:50
created

AbstractResource::isCountable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 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 implements Resource
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 int
40
     */
41
    private $rateLimitReached = 0;
42
43
    /**
44
     * AbstractResource constructor.
45
     * @param Client $client
46
     */
47 1
    public function __construct(Client $client)
48
    {
49 1
        $this->httpClient = $client;
50 1
    }
51
52
    /**
53
     * @param $method
54
     * @param $endpoint
55
     * @param array $params
56
     * @return array
57
     * @throws ClientException
58
     */
59 67
    public function request($method, $endpoint, $params = [])
60
    {
61 67
        $this->handleRateLimit();
62
63
        try {
64 67
            $response = $this->httpClient->request($method, $endpoint, $this->getRequestParameters($method, $params));
65 2
        } catch (RequestException $e) {
66 2
            $response = $e->getResponse();
67 2
            $content  = json_decode($response->getBody()->getContents(), true);
68
69 2
            throw new ClientException($content['errors'], $response->getStatusCode());
70
        }
71
72 65
        $this->setRateLimit($response->getHeader(self::API_CALL_LIMIT_HEADER));
73
74 65
        return json_decode($response->getBody()->getContents(), true);
75
    }
76
77
    /**
78
     * @return int
79
     */
80 1
    public function getRateLimitReached(): int
81
    {
82 1
        return $this->rateLimitReached;
83
    }
84
85
    /**
86
     * @param string $method
87
     * @param array $params
88
     * @return array
89
     */
90 67
    private function getRequestParameters(string $method, array $params): array
91
    {
92 67
        if ($method !== 'GET') {
93 38
            $params['headers']['Content-Type'] = 'application/json';
94
        }
95
96 67
        return $params;
97
    }
98
99 67
    private function handleRateLimit()
100
    {
101 67
        if ($this->callsMade > 0 && $this->isRateLimitReached()) {
102 1
            $this->rateLimitReached++;
103
            // Prevent bucket overflow
104
            // https://help.shopify.com/api/getting-started/api-call-limit
105 1
            usleep(rand(3, 10) * 1000000);
106
        }
107 67
    }
108
109
    /**
110
     * With a "leak rate" of 2 calls per second that continually empties the bucket.
111
     * If your app averages 2 calls per second, it will never trip a 429 error ("bucket overflow").
112
     *
113
     * @param callable $function
114
     * @return mixed
115
     */
116 10
    public function throttle(callable $function)
117
    {
118 10
        $start = time();
119
120 10
        $result = $function();
121
122 10
        $end = time();
123
124 10
        $duration = $end - $start;
125 10
        $waitTime = ceil($this->callCycle - $duration);
126
127 10
        if ($waitTime > 0) {
128 10
            sleep($waitTime);
129
        }
130
131 10
        return $result;
132
    }
133
134
    /**
135
     * @param array $header
136
     */
137 65
    private function setRateLimit(array $header)
138
    {
139 65
        $parts = explode('/', $header[0]);
140
141 65
        $this->rateLimit = $parts[1];
142 65
        $this->callsMade = $parts[0];
143 65
    }
144
145
    /**
146
     * @return bool
147
     */
148 58
    public function isRateLimitReached(): bool
149
    {
150 58
        return $this->getCallLimit() >= $this->rateLimitThreshold;
151
    }
152
153 58
    public function getCallLimit()
154
    {
155 58
        return $this->callsMade / $this->rateLimit;
156
    }
157
}
158