Completed
Pull Request — master (#38)
by Dániel
02:16
created

BattlenetHttpClient::setGuzzHandler()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
namespace Xklusive\BattlenetApi;
4
5
use GuzzleHttp\Psr7;
6
use GuzzleHttp\Client;
7
use GuzzleHttp\HandlerStack;
8
use GuzzleHttp\Psr7\Request;
9
use GuzzleHttp\Psr7\Response;
10
use Illuminate\Support\Collection;
11
use GuzzleHttp\Handler\MockHandler;
12
use GuzzleHttp\Exception\ClientException;
13
use GuzzleHttp\Exception\ServerException;
14
use GuzzleHttp\Exception\RequestException;
15
use Illuminate\Contracts\Cache\Repository;
16
17
/**
18
 * @author Guillaume Meheust <[email protected]>
19
 */
20
class BattlenetHttpClient
21
{
22
    /**
23
     * @var Repository
24
     */
25
    protected $cache;
26
27
    /**
28
     * @var string
29
     */
30
    protected $cacheKey = 'xklusive.battlenetapi.cache';
31
32
    /**
33
     * Http client.
34
     *
35
     * @var object GuzzleHttp\Client
36
     */
37
    protected $client;
38
39
    /**
40
     * Game name for url prefix.
41
     *
42
     * @var string
43
     */
44
    protected $gameParam;
45
46
    /**
47
     * Battle.net Connection Options.
48
     *
49
     * @var Collection
50
     */
51
    protected $options;
52
53
    /**
54
     * API endpoint URL.
55
     *
56
     * @var string;
57
     */
58
    protected $apiEndPoint;
59
60
    /**
61
     * BattlnetHttpClient constructor.
62
     *
63
     * @param $repository Illuminate\Contracts\Cache\Repository
64
     */
65
    public function __construct(Repository $repository)
66
    {
67
        $this->cache = $repository;
68
        $this->client = new Client([
69
            'base_uri' => $this->getApiEndPoint(),
70
        ]);
71
    }
72
73
    /**
74
     * Creates a Mock Response based on the given array.
75
     * Used to imitate API response from Blizzard, without calling the actual API.
76
     *
77
     * @param $responses array
78
     */
79
    public function createMockResponse(array $responses = null)
80
    {
81
        $returnStack = collect([]);
82
83
        if ($responses) {
84
            foreach ($responses as $response) {
85
                if ($response->has('code') and $response->has('response')) {
86
                    $stream = Psr7\stream_for($response->get('response'));
87
                    $api_response = new Response(
88
                        $response->get('code'),
89
                        ['Content-Type' => 'application/json'],
90
                        $stream
91
                    );
92
                    $returnStack->push($api_response);
93
                }
94
            }
95
        }
96
97
        $mock = new MockHandler($returnStack->toArray());
98
        $this->setGuzzHandler(HandlerStack::create($mock));
99
    }
100
101
    /**
102
     * Create a new client with the given handler.
103
     * Right now only used for testing.
104
     *
105
     * @param $handler GuzzleHttp\HandlerStack
106
     */
107
    protected function setGuzzHandler(HandlerStack $handler = null)
108
    {
109
        if ($handler) {
110
            $this->client = new Client([
111
                'handler' => $handler,
112
                'base_uri' => $this->getApiEndPoint(),
113
            ]);
114
        }
115
    }
116
117
    /**
118
     * Make request with API url and specific URL suffix.
119
     *
120
     * @return Collection|ClientException
121
     */
122
    protected function api()
123
    {
124
        $maxAttempts = 0;
125
        $attempts = 0;
126
        $statusCode = null;
127
        $reasonPhrase = null;
128
129
        $serverCodes = collect([
130
            '504' => collect([
131
                'message' => 'Gateway Time-out',
132
                'retry' => 3,
133
            ]),
134
        ]);
135
136
        do {
137
            try {
138
                $response = $this->client->get($this->apiEndPoint, $this->options->toArray());
139
                $response = collect(json_decode($response->getBody()->getContents()));
140
141
                if ($attempts > 0) {
142
                    $response->put('attempts', $attempts);
143
                }
144
145
                return $response;
146
            } catch (ServerException $e) {
147
                // Catch Server errors ( return code 5xx )
148
                if ($e->hasResponse()) {
149
                    $statusCode = $e->getResponse()->getStatusCode();
150
                    $reasonPhrase = $e->getResponse()->getReasonPhrase();
151
                }
152
153
                if ($serverCodes->has($statusCode)) {
154
                    if ($serverCodes->get($statusCode)->get('message') == $reasonPhrase) {
155
                        $maxAttempts = $serverCodes->get($statusCode)->get('retry');
156
                        $attempts++;
157
                        continue;
158
                    }
159
                }
160
            } catch (ClientException $e) {
161
                // @TODO: Handle the ClientException ( HTTP 4xx codes )
162
                if ($e->hasResponse()) {
163
                    $statusCode = $e->getResponse()->getStatusCode();
164
                    $reasonPhrase = $e->getResponse()->getReasonPhrase();
165
                }
166
            } catch (RequestException $e) {
167
                // @TODO: Handle the RequestException ( when the provided domain is not valid )
168
                if ($e->hasResponse()) {
169
                    $statusCode = $e->getResponse()->getStatusCode();
170
                    $reasonPhrase = $e->getResponse()->getReasonPhrase();
171
                } else {
172
                    $statusCode = 909;
173
                    $reasonPhrase = 'Unable to resolve API domain';
174
                }
175
            }
176
        } while ($attempts < $maxAttempts);
177
178
        if ($statusCode and $reasonPhrase) {
179
            return collect([
180
            'error' => collect([
181
                'code' => $statusCode,
182
                'message' => $reasonPhrase,
183
                'attempts' => $attempts,
184
            ]),
185
        ]);
186
        }
187
188
        throw $e;
189
    }
190
191
    /**
192
     * Cache the api response data if cache set to true in config file.
193
     *
194
     * @param array  $options   Options
195
     * @param  string $method   method name
196
     * @param string $apiEndPoint
197
     * @return Collection|ClientException
198
     */
199
    public function cache($apiEndPoint, array $options, $method)
200
    {
201
        // Make sure the options we got is a collection
202
        $options = $this->wrapCollection($options);
203
204
        $this->options = $this->getQueryOptions($options);
205
        $this->apiEndPoint = $this->gameParam.$apiEndPoint;
206
207
        $this->buildCahceOptions($method);
208
209
        if ($this->options->has('cache')) {
210
            // The cache options are defined we need to cache the results
211
            return $this->cache->remember(
212
                $this->options->get('cache')->get('uniqKey'),
213
                $this->options->get('cache')->get('duration'),
214
                function () {
215
                    return $this->api();
216
                }
217
            );
218
        }
219
220
        return $this->api();
221
    }
222
223
    /**
224
     * Get default query options from configuration file.
225
     *
226
     * @return Collection
227
     */
228
    private function getDefaultOptions()
229
    {
230
        return collect([
231
            'locale' => $this->getLocale(),
232
            'apikey' => $this->getApiKey(),
233
        ]);
234
    }
235
236
    /**
237
     * Set default option if a 'query' key is provided
238
     * else create 'query' key with default options.
239
     *
240
     * @param Collection $options
241
     *
242
     * @return Collection api response
243
     */
244
    private function getQueryOptions(Collection $options)
245
    {
246
        // Make sure the query object is a collection.
247
        $query = $this->wrapCollection($options->get('query'));
248
249
        foreach ($this->getDefaultOptions() as $key => $option) {
250
            if ($query->has($key) === false) {
251
                $query->put($key, $option);
252
            }
253
        }
254
255
        $options->put('query', $query);
256
257
        return $options;
258
    }
259
260
    /**
261
     * Get API domain provided in configuration.
262
     *
263
     * @return string
264
     */
265
    private function getApiEndPoint()
266
    {
267
        return config('battlenet-api.domain');
268
    }
269
270
    /**
271
     * Get API key provided in configuration.
272
     *
273
     * @return string
274
     */
275
    private function getApiKey()
276
    {
277
        return config('battlenet-api.api_key');
278
    }
279
280
    /**
281
     * Get API locale provided in configuration.
282
     *
283
     * @return string
284
     */
285
    private function getLocale()
286
    {
287
        return config('battlenet-api.locale', 'eu');
288
    }
289
290
    /**
291
     * This method wraps the given value in a collection when applicable.
292
     *
293
     * @return Collection
294
     */
295
    public function wrapCollection($collection)
296
    {
297
        if (is_a($collection, Collection::class) === true) {
298
            return $collection;
299
        }
300
301
        return collect($collection);
302
    }
303
304
    /**
305
     * Build the cache configuration.
306
     *
307
     * @param string $method
308
     */
309
    private function buildCahceOptions($method)
310
    {
311
        if (config('battlenet-api.cache', true)) {
312
            if ($this->options->has('cache') === false) {
313
                // We don't have any cache options yet, build it from ground up.
314
                $cacheOptions = collect();
315
316
                $cacheOptions->put('method', snake_case($method));
317
                $cacheOptions->put('uniqKey', implode('.', [$this->cacheKey, $cacheOptions->get('method')]));
318
                $cacheOptions->put('duration', config('battlenet-api.cache_duration', 600));
319
320
                $this->options->put('cache', $cacheOptions);
321
            }
322
        }
323
    }
324
}
325