Client   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 278
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
dl 0
loc 278
rs 9.92
c 0
b 0
f 0
wmc 31
lcom 1
cbo 12

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 3
A setLogger() 0 4 1
A setAPIRoot() 0 4 1
A setCredentials() 0 5 1
A getCredentials() 0 4 1
A authorize() 0 23 2
A get() 0 4 1
A post() 0 4 1
A put() 0 4 1
B makeAPIRequest() 0 45 6
A resolveEntryPoint() 0 13 2
A getEntryPoint() 0 14 3
A getEntrypointResource() 0 19 2
A getCache() 0 7 2
A setCache() 0 4 1
A getClient() 0 16 3
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
    public function __construct(HTTPClientFactory $clientFactory = null)
67
    {
68
        if (isset($_SERVER['LIBLYNX_CLIENT_ID'])) {
69
            $this->clientId = $_SERVER['LIBLYNX_CLIENT_ID'];
70
        }
71
        if (isset($_SERVER['LIBLYNX_CLIENT_SECRET'])) {
72
            $this->clientSecret = $_SERVER['LIBLYNX_CLIENT_SECRET'];
73
        }
74
75
        $this->log = new NullLogger();
76
        $this->httpClientFactory = $clientFactory ?? new HTTPClientFactory;
77
    }
78
79
    /**
80
     * @inheritdoc
81
     */
82
    public function setLogger(LoggerInterface $logger)
83
    {
84
        $this->log = $logger;
85
    }
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
    public function setAPIRoot($url)
93
    {
94
        $this->apiroot = $url;
95
    }
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
    public function setCredentials($clientId, $clientSecret)
105
    {
106
        $this->clientId = $clientId;
107
        $this->clientSecret = $clientSecret;
108
    }
109
110
    /**
111
     * @return array containing client id and secret
112
     */
113
    public function getCredentials()
114
    {
115
        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
    public function authorize(IdentificationRequest $request)
125
    {
126
        $payload = $request->getRequestJSON();
127
        $response = $this->post('@new_identification', $payload);
128
        if (!isset($response->id)) {
129
            //failed
130
            $this->log->critical('Identification request failed {payload}', ['payload' => $payload]);
131
            return null;
132
        }
133
134
        $identification = new Identification($response);
135
        $this->log->info(
136
            'Identification request for ip {ip} on URL {url} succeeded status={status} id={id}',
137
            [
138
                'status' => $identification->status,
139
                'id' => $identification->id,
140
                'ip' => $identification->ip,
141
                'url' => $identification->url
142
            ]
143
        );
144
145
        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
    public function get($entrypoint)
154
    {
155
        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
    public function post($entrypoint, $json)
165
    {
166
        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
    protected function makeAPIRequest($method, $entrypoint, $json = null)
189
    {
190
        $this->log->debug('{method} {entry} {json}', ['method' => $method, 'entry' => $entrypoint, 'json' => $json]);
191
        $url = $this->resolveEntryPoint($entrypoint);
192
        $client = $this->getClient();
193
194
        $headers = ['Accept' => 'application/json'];
195
        if (!empty($json)) {
196
            $headers['Content-Type'] = 'application/json';
197
        }
198
199
        $request = new Request($method, $url, $headers, $json);
200
201
        try {
202
            $response = $client->send($request);
203
            $this->log->debug(
204
                '{method} {entry} succeeded {status}',
205
                ['method' => $method, 'entry' => $entrypoint, 'status' => $response->getStatusCode()]
206
            );
207
        } catch (RequestException $e) {
208
            //we usually have a response available, but it's not guaranteed
209
            $response = $e->getResponse();
210
            $this->log->error(
211
                '{method} {entrypoint} {json} failed ({status}): {body}',
212
                [
213
                    'method' => $method,
214
                    'json' => $json,
215
                    'entrypoint' => $entrypoint,
216
                    'status' => $response ? $response->getStatusCode() : 0,
217
                    'body' => $response ? $response->getBody() : ''
218
                ]
219
            );
220
221
            throw new APIException("$method $entrypoint request failed", $e->getCode(), $e);
222
        } catch (GuzzleException $e) {
223
            $this->log->critical(
224
                '{method} {entry} {json} failed',
225
                ['method' => $method, 'json' => $json, 'entry' => $entrypoint]
226
            );
227
228
            throw new APIException("$method $entrypoint failed", 0, $e);
229
        }
230
231
        return json_decode($response->getBody());
232
    }
233
234
    public function resolveEntryPoint($nameOrUrl)
235
    {
236
        if ($nameOrUrl[0] === '@') {
237
            $resolved = $this->getEntryPoint($nameOrUrl);
238
            $this->log->debug(
239
                'Entrypoint {entrypoint} resolves to {url}',
240
                ['entrypoint' => $nameOrUrl, 'url' => $resolved]
241
            );
242
            return $resolved;
243
        }
244
        //it's a URL
245
        return $nameOrUrl;
246
    }
247
248
    public function getEntryPoint($name)
249
    {
250
        if (!is_array($this->entrypoint)) {
251
            $this->entrypoint = $this->getEntrypointResource();
252
        } else {
253
            $this->log->debug('using previously loaded entrypoint');
254
        }
255
256
        if (!isset($this->entrypoint->_links->$name->href)) {
257
            throw new LogicException("Invalid LibLynx API entrypoint $name requested");
258
        }
259
260
        return $this->entrypoint->_links->$name->href;
261
    }
262
263
    protected function getEntrypointResource()
264
    {
265
        $key = 'entrypoint' . $this->clientId;
266
267
        $cache = $this->getCache();
268
        if ($cache->has($key)) {
269
            $this->log->debug('loading entrypoint from persistent cache');
270
            $entrypointResource = $cache->get($key);
271
            ;
272
        } else {
273
            $this->log->debug('entrypoint not cached, requesting from API');
274
            $entrypointResource = $this->get('api');
275
            $cache->set($key, $entrypointResource, 86400);
276
            $this->log->info('entrypoint loaded from API and cached');
277
        }
278
279
280
        return $entrypointResource;
281
    }
282
283
    public function getCache()
284
    {
285
        if (is_null($this->cache)) {
286
            throw new LogicException('LibLynx Connect Client requires a PSR-16 compatible cache');
287
        }
288
        return $this->cache;
289
    }
290
291
    public function setCache(CacheInterface $cache)
292
    {
293
        $this->cache = $cache;
294
    }
295
296
    /**
297
     * Internal helper to provide an OAuth2 capable HTTP client
298
     */
299
    protected function getClient()
300
    {
301
        if (empty($this->clientId)) {
302
            throw new LogicException('Cannot make API calls until setCredentials has been called');
303
        }
304
        if (!is_object($this->guzzle)) {
305
            $this->guzzle = $this->httpClientFactory->create(
306
                $this->apiroot,
307
                $this->clientId,
308
                $this->clientSecret,
309
                $this->getCache()
310
            );
311
        }
312
313
        return $this->guzzle;
314
    }
315
}
316