Completed
Branch cleanup (1408c8)
by Paul
04:39
created

Client::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 4
nop 1
dl 0
loc 12
ccs 8
cts 8
cp 1
crap 3
rs 9.4285
c 0
b 0
f 0
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 4
        if (!isset($response->id)) {
129
            //failed
130 2
            $this->log->critical('Identification request failed {payload}', ['payload' => $payload]);
131 2
            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
    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 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 8
    protected function makeAPIRequest($method, $entrypoint, $json = null)
189
    {
190 8
        $this->log->debug('{method} {entrypoint} {json}', [
191 8
            'method' => $method,
192 8
            'entrypoint' => $entrypoint,
193 8
            'json' => $json
194
        ]);
195 8
        $url = $this->resolveEntryPoint($entrypoint);
196 4
        $client = $this->getClient();
197
198 4
        $this->log->debug('{entrypoint} = {url}', ['entrypoint' => $entrypoint, 'url' => $url]);
199
200 4
        $headers = ['Accept' => 'application/json'];
201 4
        if (!empty($json)) {
202 4
            $headers['Content-Type'] = 'application/json';
203
        }
204
205 4
        $request = new Request($method, $url, $headers, $json);
206
207
        try {
208 4
            $response = $client->send($request);
209
210 2
            $this->log->debug('{method} {entrypoint} succeeded {status}', [
211 2
                'method' => $method,
212 2
                'entrypoint' => $entrypoint,
213 2
                'status' => $response->getStatusCode(),
214
            ]);
215 2
        } catch (RequestException $e) {
216 2
            $response = $e->getResponse();
217 2
            $this->log->error(
218 2
                '{method} {entrypoint} {json} failed ({status}): {body}',
219
                [
220 2
                    'method' => $method,
221 2
                    'json' => $json,
222 2
                    'entrypoint' => $entrypoint,
223 2
                    'status' => $response->getStatusCode(),
224 2
                    'body' => $response->getBody()
225
                ]
226
            );
227
        } catch (GuzzleException $e) {
228
            $this->log->critical(
229
                '{method} {entrypoint} {json} failed',
230
                [
231
                    'method' => $method,
232
                    'json' => $json,
233
                    'entrypoint' => $entrypoint,
234
                ],
235
                $e
0 ignored issues
show
Unused Code introduced by
The call to LoggerInterface::critical() has too many arguments starting with $e.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
236
            );
237
238
            throw new APIException();
239
        }
240
241 4
        $payload = json_decode($response->getBody());
242 4
        return $payload;
243
    }
244
245 8
    public function resolveEntryPoint($nameOrUrl)
246
    {
247 8
        if ($nameOrUrl[0] === '@') {
248 8
            return $this->getEntryPoint($nameOrUrl);
249
        }
250
        //it's a URL
251
        return $nameOrUrl;
252
    }
253
254 14
    public function getEntryPoint($name)
255
    {
256 14
        if (!is_array($this->entrypoint)) {
257 14
            $this->entrypoint = $this->loadEntrypointResource();
258
        } else {
259
            $this->log->debug('using previously loaded entrypoint');
260
        }
261
262 8
        if (!isset($this->entrypoint->_links->$name->href)) {
263 2
            throw new LogicException("Invalid LibLynx API entrypoint $name requested");
264
        }
265
266 6
        return $this->entrypoint->_links->$name->href;
267
    }
268
269 14
    public function loadEntrypointResource()
270
    {
271 14
        $entrypointResource = null;
272 14
        $key = 'entrypoint' . $this->clientId;
273
274 14
        $cache = $this->getCache();
275 12
        if ($cache->has($key)) {
276 2
            $this->log->debug('loading entrypoint from persistent cache');
277 2
            $entrypointResource = $cache->get($key);
278
            ;
279
        } else {
280 12
            $this->log->debug('entrypoint not cached, requesting from API');
281 12
            $client = $this->getClient();
282
283 10
            $request = new Request('GET', 'api', [
284 10
                'Content-Type' => 'application/json',
285
                'Accept' => 'application/json',
286
            ]);
287
288
            try {
289 10
                $response = $client->send($request);
290
291 8
                $payload = json_decode($response->getBody());
292 8
                if (is_object($payload) && isset($payload->_links)) {
293 8
                    $this->log->info('entrypoint loaded from API and cached');
294 8
                    $entrypointResource = $payload;
295
296 8
                    $cache->set($key, $payload, 86400);
297
                }
298 2
            } catch (GuzzleException $e) {
299 2
                throw new APIException("Unable to obtain LibLynx API entry point resource", 0, $e);
300
            }
301
        }
302 8
        return $entrypointResource;
303
    }
304
305
306 14
    public function getCache()
307
    {
308 14
        if (is_null($this->cache)) {
309 2
            throw new LogicException('LibLynx Connect Client requires a PSR-16 compatible cache');
310
        }
311 12
        return $this->cache;
312
    }
313
314 12
    public function setCache(CacheInterface $cache)
315
    {
316 12
        $this->cache = $cache;
317 12
    }
318
319
    /**
320
     * Internal helper to provide an OAuth2 capable HTTP client
321
     */
322 12
    protected function getClient()
323
    {
324 12
        if (empty($this->clientId)) {
325 2
            throw new LogicException('Cannot make API calls until setCredentials has been called');
326
        }
327 10
        if (!is_object($this->guzzle)) {
328 10
            $this->guzzle = $this->httpClientFactory->create(
329 10
                $this->apiroot,
330 10
                $this->clientId,
331 10
                $this->clientSecret,
332 10
                $this->getCache()
333
            );
334
        }
335
336 10
        return $this->guzzle;
337
    }
338
}
339