Passed
Push — master ( 84f46e...e41bcf )
by Dan Michael O.
02:23
created

Client::setKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 9.4285
c 1
b 0
f 0
1
<?php
2
3
namespace Scriptotek\Alma;
4
5
use Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement;
6
use GuzzleHttp\Client as HttpClient;
7
use GuzzleHttp\Exception\ClientException as GuzzleClientException;
8
use GuzzleHttp\Exception\RequestException;
9
use Scriptotek\Alma\Analytics\Analytics;
10
use Scriptotek\Alma\Bibs\Bibs;
11
use Scriptotek\Alma\Exception\ClientException;
12
use Scriptotek\Alma\Exception\SruClientNotSetException;
13
use Scriptotek\Alma\Users\Users;
14
use Scriptotek\Sru\Client as SruClient;
15
16
/**
17
 * Alma client.
18
 */
19
class Client
20
{
21
    public $baseUrl;
22
23
    /** @var string Alma zone (institution or network) */
24
    public $zone;
25
26
    /** @var string Alma Developers Network API key for this zone */
27
    public $key;
28
29
    /** @var Client Network zone instance */
30
    public $nz;
31
32
    /** @var HttpClient */
33
    protected $httpClient;
34
35
    /** @var SruClient */
36
    public $sru;
37
38
    /** @var Bibs */
39
    public $bibs;
40
41
    /** @var Analytics */
42
    public $analytics;
43
44
    /** @var Users */
45
    public $users;
46
47
    /**
48
     * Create a new client to connect to a given Alma instance.
49
     *
50
     * @param string     $key        API key
51
     * @param string     $region     Hosted region code, used to build base URL
52
     * @param string     $zone       Alma zone (Either Zones::INSTITUTION or Zones::NETWORK)
53
     * @param HttpClient $httpClient
54
     *
55
     * @throws \ErrorException
56
     */
57
    public function __construct($key = null, $region = 'eu', $zone = Zones::INSTITUTION, HttpClient $httpClient = null)
58
    {
59
        $this->key = $key;
60
        $this->setRegion($region);
61
        $this->httpClient = $httpClient ?: new HttpClient();
62
        $this->zone = $zone;
63
        $this->bibs = new Bibs($this);  // Or do some magic instead?
64
        $this->analytics = new Analytics($this);  // Or do some magic instead?
65
        $this->users = new Users($this);  // Or do some magic instead?
66
        if ($zone == Zones::INSTITUTION) {
67
            $this->nz = new self(null, $region, Zones::NETWORK, $this->httpClient);
68
        } elseif ($zone != Zones::NETWORK) {
69
            throw new ClientException('Invalid zone name.');
70
        }
71
    }
72
73
    /**
74
     * Attach an SRU client (so you can search for Bib records).
75
     *
76
     * @param SruClient $sru
77
     */
78
    public function setSruClient(SruClient $sru)
79
    {
80
        $this->sru = $sru;
81
    }
82
83
    /**
84
     * Assert that an SRU client is connected. Throws SruClientNotSetException if not.
85
     *
86
     * @throws SruClientNotSetException
87
     */
88
    public function assertHasSruClient()
89
    {
90
        if (!isset($this->sru)) {
91
            throw new SruClientNotSetException();
92
        }
93
    }
94
95
    /**
96
     * Set the API key for this Alma instance.
97
     *
98
     * @param string $key The API key
99
     * @return $this
100
     */
101
    public function setKey($key)
102
    {
103
        $this->key = $key;
104
105
        return $this;
106
    }
107
108
    /**
109
     * Set the Alma region code ('na' for North America, 'eu' for Europe, 'ap' for Asia Pacific).
110
     *
111
     * @param $regionCode
112
     * @return $this
113
     * @throws \ErrorException
114
     */
115
    public function setRegion($regionCode)
116
    {
117
        if (!in_array($regionCode, ['na', 'eu', 'ap'])) {
118
            throw new ClientException('Invalid region code');
119
        }
120
        $this->baseUrl = 'https://api-' . $regionCode . '.hosted.exlibrisgroup.com/almaws/v1';
121
122
        return $this;
123
    }
124
125
    /**
126
     * @param $url
127
     *
128
     * @return string
129
     */
130
    protected function getFullUrl($url)
131
    {
132
        return $this->baseUrl . $url;
133
    }
134
135
    /**
136
     * @param array $options
137
     *
138
     * @return array
139
     */
140
    protected function getHttpOptions($options = [])
141
    {
142
        if (!$this->key) {
143
            throw new ClientException('No API key defined for ' . $this->zone);
144
        }
145
        $defaultOptions = [
146
            'headers' => ['Authorization' => 'apikey ' . $this->key],
147
        ];
148
149
        return array_merge_recursive($defaultOptions, $options);
150
    }
151
152
    /**
153
     * Make a HTTP request.
154
     *
155
     * @param string $method
156
     * @param string $url
157
     * @param array  $options
158
     *
159
     * @return \Psr\Http\Message\ResponseInterface
160
     */
161
    public function request($method, $url, $options = [])
162
    {
163
        try {
164
            return $this->httpClient->request($method, $this->getFullUrl($url), $this->getHttpOptions($options));
165
        } catch (GuzzleClientException $e) {
166
            $this->handleError($e->getResponse());
0 ignored issues
show
Documentation introduced by
$e->getResponse() is of type object<Psr\Http\Message\ResponseInterface>|null, but the function expects a object<GuzzleHttp\Exception\ClientException>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
167
        }
168
    }
169
170
    public function handleError(GuzzleClientException $response)
171
    {
172
        // TODO: If we get a 429 response, it means we have run into the
173
        // "Max 25 API calls per institution per second" limit. In that case we should just wait a sec and retry
174
        $msg = $response->getBody();
0 ignored issues
show
Bug introduced by
The method getBody() does not seem to exist on object<GuzzleHttp\Exception\ClientException>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
175
        throw new ClientException('Client error ' . $response->getStatusCode() . ': ' . $msg);
0 ignored issues
show
Bug introduced by
The method getStatusCode() does not seem to exist on object<GuzzleHttp\Exception\ClientException>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
176
    }
177
178
    /**
179
     * Make a GET request.
180
     *
181
     * @param string $url
182
     * @param array  $query
183
     * @param string $contentType
184
     * @return string  Response body
185
     */
186
    public function get($url, $query = [], $contentType = 'application/json')
187
    {
188
        $response = $this->request('GET', $url, [
189
            'query'   => $query,
190
            'headers' => ['Accept' => $contentType],
191
        ]);
192
193
        return strval($response->getBody());
194
    }
195
196
    /**
197
     * Make a GET request, accepting JSON.
198
     *
199
     * @param string $url
200
     * @param array  $query
201
     *
202
     * @return \stdClass  JSON response as an object.
203
     */
204
    public function getJSON($url, $query = [])
205
    {
206
        $responseBody = $this->get($url, $query, 'application/json');
207
208
        return json_decode($responseBody);
209
    }
210
211
    /**
212
     * Make a GET request, accepting XML.
213
     *
214
     * @param string $url
215
     * @param array  $query
216
     *
217
     * @return QuiteSimpleXMLElement
218
     */
219
    public function getXML($url, $query = [])
220
    {
221
        $responseBody = $this->get($url, $query, 'application/xml');
222
223
        return new QuiteSimpleXMLElement($responseBody);
224
    }
225
226
    /**
227
     * Make a PUT request.
228
     *
229
     * @param string $url
230
     * @param $data
231
     * @param string $contentType
232
     * @return bool
233
     */
234
    public function put($url, $data, $contentType = 'application/json')
235
    {
236
        $response = $this->request('PUT', $url, [
237
            'body'    => $data,
238
            'headers' => [
239
                'Content-Type' => $contentType,
240
                'Accept'       => $contentType,
241
            ],
242
        ]);
243
244
        // Consider it a success if status code is 2XX
245
        return substr($response->getStatusCode(), 0, 1) == '2';
246
    }
247
248
    /**
249
     * Make a PUT request, sending JSON data.
250
     *
251
     * @param string $url
252
     * @param $data
253
     *
254
     * @return bool
255
     */
256
    public function putJSON($url, $data)
257
    {
258
        $data = json_encode($data);
259
260
        return $this->put($url, $data, 'application/json');
261
    }
262
263
    /**
264
     * Make a PUT request, sending XML data.
265
     *
266
     * @param string $url
267
     * @param $data
268
     *
269
     * @return bool
270
     */
271
    public function putXML($url, $data)
272
    {
273
        return $this->put($url, $data, 'application/xml');
274
    }
275
276
    /**
277
     * Get the redirect target location of an URL, or null if not a redirect.
278
     *
279
     * @param string $url
280
     * @param array  $query
281
     *
282
     * @return string|null
283
     */
284
    public function getRedirectLocation($url, $query = [])
285
    {
286
        try {
287
            $response = $this->httpClient->request('GET', $this->getFullUrl($url), $this->getHttpOptions([
288
                'query'           => $query,
289
                'headers'         => ['Accept' => 'application/json'],
290
                'allow_redirects' => false,
291
            ]));
292
        } catch (RequestException $e) {
293
            // We receive a 400 response if the barcode is invalid.
294
            return null;
295
        }
296
        $locations = $response->getHeader('Location');
297
298
        return count($locations) ? $locations[0] : null;
299
    }
300
}
301