Completed
Push — develop ( 1b5cd2...b91b03 )
by Edwin
02:13
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
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 21
    public function request($method, $endpoint, $params = [])
60
    {
61 21
        $this->handleRateLimit();
62
63
        try {
64 21
            $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 19
        $this->setRateLimit($response->getHeader(self::API_CALL_LIMIT_HEADER));
73
74 19
        return json_decode($response->getBody()->getContents(), true);
75
    }
76
77
    /**
78
     * @param string $method
79
     * @param array $params
80
     * @return array
81
     */
82 21
    private function getRequestParameters(string $method, array $params): array
83
    {
84 21
        if ($method !== 'GET') {
85 14
            $params['headers']['Content-Type'] = 'application/json';
86
        }
87
88 21
        return $params;
89
    }
90
91 21
    private function handleRateLimit()
92
    {
93 21
        if ($this->callsMade > 0 && $this->isRateLimitReached()) {
94
            // Prevent bucket overflow
95
            // https://help.shopify.com/api/getting-started/api-call-limit
96
            usleep(rand(3, 10) * 1000000);
97
        }
98 21
    }
99
100
    /**
101
     * With a "leak rate" of 2 calls per second that continually empties the bucket.
102
     * If your app averages 2 calls per second, it will never trip a 429 error ("bucket overflow").
103
     *
104
     * @param callable $function
105
     * @return mixed
106
     */
107 2
    public function throttle(callable $function)
108
    {
109 2
        $start = time();
110
111 2
        $result = $function();
112
113 2
        $end = time();
114
115 2
        $duration = $end - $start;
116 2
        $waitTime = ceil($this->callCycle - $duration);
117
118 2
        if ($waitTime > 0) {
119 2
            sleep($waitTime);
120
        }
121
122 2
        return $result;
123
    }
124
125
    /**
126
     * @param array $header
127
     */
128 19
    private function setRateLimit(array $header)
129
    {
130 19
        $parts = explode('/', $header[0]);
131
132 19
        $this->rateLimit = $parts[1];
133 19
        $this->callsMade = $parts[0];
134 19
    }
135
136
    /**
137
     * @return bool
138
     */
139 20
    public function isRateLimitReached(): bool
140
    {
141 20
        return $this->getCallLimit() >= $this->rateLimitThreshold;
142
    }
143
144 20
    public function getCallLimit()
145
    {
146 20
        return $this->callsMade / $this->rateLimit;
147
    }
148
149
    /**
150
     * @return bool
151
     */
152 3
    public function isCountable(): bool
153
    {
154 3
        return $this->countable;
155
    }
156
}
157