Completed
Branch cleanup (04d52f)
by Paul
01:50
created

Client::get()   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
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
namespace LibLynx\Connect;
4
5
use GuzzleHttp\ClientInterface;
6
use GuzzleHttp\Exception\RequestException;
7
use GuzzleHttp\Exception\GuzzleException;
8
use GuzzleHttp\Psr7\Request;
9
use LibLynx\Connect\Exception\APIException;
10
use LibLynx\Connect\Exception\LogicException;
11
use LibLynx\Connect\HTTPClient\HTTPClientFactory;
12
use LibLynx\Connect\Resource\Identification;
13
use Psr\Log\LoggerAwareInterface;
14
use Psr\Log\LoggerInterface;
15
use Psr\Log\NullLogger;
16
use Psr\SimpleCache\CacheInterface;
17
18
/**
19
 * LibLynx Connect API client
20
 *
21
 * $liblynx=new Liblynx\Connect\Client;
22
 * $liblynx->setCredentials('your client id', 'your client secret');
23
 *
24
 * //must set a PSR-16 cache - this can be used for testing
25
 * $liblynx->setCache(new \Symfony\Component\Cache\Simple\ArrayCache);
26
 *
27
 * $identification=$liblynx->authorize(Identification::fromSuperglobals());
28
 * if ($identification->isIdentified()) {
29
 *     //good to go
30
 * }
31
 *
32
 * if ($identification->requiresWayf()) {
33
 *     $identification->doWayfRedirect();
34
 * }
35
 *
36
 * @package LibLynx\Connect
37
 */
38
class Client implements LoggerAwareInterface
39
{
40
    private $apiroot = 'https://connect.liblynx.com';
41
42
    /** @var string client ID obtain from LibLynx Connect admin portal */
43
    private $clientId;
44
45
    /** @var string client secret obtain from LibLynx Connect admin portal */
46
    private $clientSecret;
47
48
    /** @var ClientInterface HTTP client for API requests */
49
    private $guzzle;
50
51
    /** @var \stdClass entry point resource */
52
    private $entrypoint;
53
54
    /** @var CacheInterface */
55
    protected $cache;
56
57
    /** @var LoggerInterface */
58
    protected $log;
59
60
    /** @var HTTPClientFactory */
61
    protected $httpClientFactory;
62
63
    /**
64
     * Create new LibLynx API client
65
     */
66 16
    public function __construct(HTTPClientFactory $clientFactory = null)
67
    {
68 16
        if (isset($_SERVER['LIBLYNX_CLIENT_ID'])) {
69 2
            $this->clientId = $_SERVER['LIBLYNX_CLIENT_ID'];
70
        }
71 16
        if (isset($_SERVER['LIBLYNX_CLIENT_SECRET'])) {
72 2
            $this->clientSecret = $_SERVER['LIBLYNX_CLIENT_SECRET'];
73
        }
74
75 16
        $this->log = new NullLogger();
76 16
        $this->httpClientFactory = $clientFactory ?? new HTTPClientFactory;
77 16
    }
78
79
    /**
80
     * @inheritdoc
81
     */
82 2
    public function setLogger(LoggerInterface $logger)
83
    {
84 2
        $this->log = $logger;
85 2
    }
86
87
    /**
88
     * Set API root
89
     * An alternative root may be provided to you by LibLynx Technical Support
90
     * @param string $url
91
     */
92 10
    public function setAPIRoot($url)
93
    {
94 10
        $this->apiroot = $url;
95 10
    }
96
97
    /**
98
     * Set OAuth2.0 client credentials
99
     * These will be provided to you by LibLynx Technical Support
100
     *
101
     * @param string $clientId
102
     * @param string $clientSecret
103
     */
104 12
    public function setCredentials($clientId, $clientSecret)
105
    {
106 12
        $this->clientId = $clientId;
107 12
        $this->clientSecret = $clientSecret;
108 12
    }
109
110
    /**
111
     * @return array containing client id and secret
112
     */
113 2
    public function getCredentials()
114
    {
115 2
        return [$this->clientId, $this->clientSecret];
116
    }
117
118
    /**
119
     * Attempt an identification using the LibLynx API
120
     *
121
     * @param IdentificationRequest $request
122
     * @return Identification|null
123
     */
124 8
    public function authorize(IdentificationRequest $request)
125
    {
126 8
        $payload = $request->getRequestJSON();
127 8
        $response = $this->post('@new_identification', $payload);
128 2
        if (!isset($response->id)) {
129
            //failed
130
            $this->log->critical('Identification request failed {payload}', ['payload' => $payload]);
131
            return null;
132
        }
133
134 2
        $identification = new Identification($response);
135 2
        $this->log->info(
136 2
            'Identification request for ip {ip} on URL {url} succeeded status={status} id={id}',
137
            [
138 2
                'status' => $identification->status,
139 2
                'id' => $identification->id,
140 2
                'ip' => $identification->ip,
141 2
                'url' => $identification->url
142
            ]
143
        );
144
145 2
        return $identification;
146
    }
147
148
    /**
149
     * General purpose 'GET' request against API
150
     * @param $entrypoint string contains either an @entrypoint or full URL obtained from a resource
151
     * @return mixed
152
     */
153 12
    public function get($entrypoint)
154
    {
155 12
        return $this->makeAPIRequest('GET', $entrypoint);
156
    }
157
158
    /**
159
     * General purpose 'POST' request against API
160
     * @param $entrypoint string contains either an @entrypoint or full URL obtained from a resource
161
     * @param $json string contains JSON formatted data to post
162
     * @return mixed
163
     */
164 8
    public function post($entrypoint, $json)
165
    {
166 8
        return $this->makeAPIRequest('POST', $entrypoint, $json);
167
    }
168
169
    /**
170
     * General purpose 'PUT' request against API
171
     * @param $entrypoint string contains either an @entrypoint or full URL obtained from a resource
172
     * @return mixed string contains JSON formatted data to put
173
     */
174
    public function put($entrypoint, $json)
175
    {
176
        return $this->makeAPIRequest('PUT', $entrypoint, $json);
177
    }
178
179
    /**
180
     * @param $method
181
     * @param $entrypoint
182
     * @param null $json
183
     * @return \stdClass object containing JSON decoded response - note this can be an error response for normally
184
     *         handled errors
185
     * @throws LogicException for integration errors, e.g. not setting a cache
186
     * @throws APIException for unexpected API failures
187
     */
188 14
    protected function makeAPIRequest($method, $entrypoint, $json = null)
189
    {
190 14
        $this->log->debug('{method} {entrypoint} {json}', [
191 14
            'method' => $method,
192 14
            'entrypoint' => $entrypoint,
193 14
            'json' => $json
194
        ]);
195 14
        $url = $this->resolveEntryPoint($entrypoint);
196 12
        $client = $this->getClient();
197
198
199 10
        $headers = ['Accept' => 'application/json'];
200 10
        if (!empty($json)) {
201 4
            $headers['Content-Type'] = 'application/json';
202
        }
203
204 10
        $request = new Request($method, $url, $headers, $json);
205
206
        try {
207 10
            $response = $client->send($request);
208
209 8
            $this->log->debug('{method} {entrypoint} succeeded {status}', [
210 8
                'method' => $method,
211 8
                'entrypoint' => $entrypoint,
212 8
                'status' => $response->getStatusCode(),
213
            ]);
214 4
        } catch (RequestException $e) {
215
            //we usually have a response available, but it's not guaranteed
216 4
            $response = $e->getResponse();
217 4
            $this->log->error(
218 4
                '{method} {entrypoint} {json} failed ({status}): {body}',
219
                [
220 4
                    'method' => $method,
221 4
                    'json' => $json,
222 4
                    'entrypoint' => $entrypoint,
223 4
                    'status' => $response ? $response->getStatusCode() : 0,
224 4
                    'body' => $response ? $response->getBody() : ''
225
                ]
226
            );
227
228 4
            throw new APIException("$method $entrypoint request failed", $e->getCode(), $e);
229
        } catch (GuzzleException $e) {
230
            $this->log->critical(
231
                '{method} {entrypoint} {json} failed',
232
                [
233
                    'method' => $method,
234
                    'json' => $json,
235
                    'entrypoint' => $entrypoint,
236
                ]
237
            );
238
239
            throw new APIException("$method $entrypoint failed", 0, $e);
240
        }
241
242 8
        $payload = json_decode($response->getBody());
243 8
        return $payload;
244
    }
245
246 14
    public function resolveEntryPoint($nameOrUrl)
247
    {
248 14
        if ($nameOrUrl[0] === '@') {
249 8
            $resolved = $this->getEntryPoint($nameOrUrl);
250 4
            $this->log->debug('Entrypoint {entrypoint} resolves to {url}', ['entrypoint' => $nameOrUrl, 'url' => $resolved]);
251 4
            return $resolved;
252
        }
253
        //it's a URL
254 12
        return $nameOrUrl;
255
    }
256
257 14
    public function getEntryPoint($name)
258
    {
259 14
        if (!is_array($this->entrypoint)) {
260 14
            $this->entrypoint = $this->getEntrypointResource();
261
        } else {
262
            $this->log->debug('using previously loaded entrypoint');
263
        }
264
265 8
        if (!isset($this->entrypoint->_links->$name->href)) {
266 2
            throw new LogicException("Invalid LibLynx API entrypoint $name requested");
267
        }
268
269 6
        return $this->entrypoint->_links->$name->href;
270
    }
271
272 14
    protected function getEntrypointResource()
273
    {
274 14
        $entrypointResource = null;
0 ignored issues
show
Unused Code introduced by
$entrypointResource is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
275 14
        $key = 'entrypoint' . $this->clientId;
276
277 14
        $cache = $this->getCache();
278 12
        if ($cache->has($key)) {
279 2
            $this->log->debug('loading entrypoint from persistent cache');
280 2
            $entrypointResource = $cache->get($key);
281
            ;
282
        } else {
283 12
            $this->log->debug('entrypoint not cached, requesting from API');
284 12
            $entrypointResource = $this->get('api');
285 8
            $cache->set($key, $entrypointResource, 86400);
286 8
            $this->log->info('entrypoint loaded from API and cached');
287
        }
288
289
290 8
        return $entrypointResource;
291
    }
292
293 14
    public function getCache()
294
    {
295 14
        if (is_null($this->cache)) {
296 2
            throw new LogicException('LibLynx Connect Client requires a PSR-16 compatible cache');
297
        }
298 12
        return $this->cache;
299
    }
300
301 12
    public function setCache(CacheInterface $cache)
302
    {
303 12
        $this->cache = $cache;
304 12
    }
305
306
    /**
307
     * Internal helper to provide an OAuth2 capable HTTP client
308
     */
309 12
    protected function getClient()
310
    {
311 12
        if (empty($this->clientId)) {
312 2
            throw new LogicException('Cannot make API calls until setCredentials has been called');
313
        }
314 10
        if (!is_object($this->guzzle)) {
315 10
            $this->guzzle = $this->httpClientFactory->create(
316 10
                $this->apiroot,
317 10
                $this->clientId,
318 10
                $this->clientSecret,
319 10
                $this->getCache()
320
            );
321
        }
322
323 10
        return $this->guzzle;
324
    }
325
}
326