Passed
Push — master ( 36df1b...6f7cd5 )
by
unknown
01:45
created

Api::handleResponseStatus()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 33
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 7
nop 1
dl 0
loc 33
ccs 15
cts 15
cp 1
crap 7
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
namespace Defro\Google\StreetView;
4
5
use Defro\Google\StreetView\Exception\BadStatusCodeException;
6
use Defro\Google\StreetView\Exception\RequestException;
7
use Defro\Google\StreetView\Exception\UnexpectedStatusException;
8
use Defro\Google\StreetView\Exception\UnexpectedValueException;
9
use GuzzleHttp\Client;
10
use GuzzleHttp\Exception\GuzzleException;
11
12
class Api
13
{
14
    /** @var string default source */
15
    const SOURCE_DEFAULT = 'default';
16
17
    /** @var string outdoor source */
18
    const SOURCE_OUTDOOR = 'outdoor';
19
20
    /** @var string */
21
    private $endpointImage = 'https://maps.googleapis.com/maps/api/streetview';
22
23
    /** @var string */
24
    private $endpointMetadata = 'https://maps.googleapis.com/maps/api/streetview/metadata';
25
26
    /** @var \GuzzleHttp\Client */
27
    private $client;
28
29
    /** @var string */
30
    private $apiKey;
31
32
    /** @var string */
33
    private $signature;
34
35
    /** @var int */
36
    private $imageWidth = 600;
37
38
    /** @var int */
39
    private $imageHeight = 600;
40
41
    /** @var int */
42
    private $heading;
43
44
    /** @var int */
45
    private $cameraFov = 90;
46
47
    /** @var int */
48
    private $cameraPitch = 0;
49
50
    /** @var int */
51
    private $radius = 50;
52
53
    /** @var string */
54
    private $source = 'default';
55
56
57
    /**
58
     * Api constructor.
59
     *
60
     * @param Client $client
61
     */
62 33
    public function __construct(Client $client)
63
    {
64 33
        $this->client = $client;
65 33
    }
66
67
    /**
68
     * API key from your Google console
69
     *
70
     * @param string $apiKey
71
     * @return $this
72
     */
73 1
    public function setApiKey(string $apiKey): self
74
    {
75 1
        $this->apiKey = $apiKey;
76
77 1
        return $this;
78
    }
79
80
    /**
81
     * Digital signature used to verify that any site generating requests.
82
     *
83
     * @param string $signature
84
     * @return $this
85
     */
86 2
    public function setSignature(string $signature): self
87
    {
88 2
        $this->signature = $signature;
89
90 2
        return $this;
91
    }
92
93
    /**
94
     * Determines the horizontal field of view of the image.
95
     * The field of view is expressed in degrees, with a maximum allowed value of 120.
96
     * When dealing with a fixed-size viewport, as with a Street View image of a set size,
97
     * field of view in essence represents zoom, with smaller numbers indicating a higher level of zoom.
98
     *
99
     * @param int $cameraFov
100
     * @return $this
101
     * @throws UnexpectedValueException
102
     */
103 2
    public function setCameraFov(int $cameraFov): self
104
    {
105 2
        if ($cameraFov > 120) {
106 1
            throw new UnexpectedValueException(
107 1
                'Camera FOV value cannot exceed 120 degrees.'
108
            );
109
        }
110
111 1
        $this->cameraFov = $cameraFov;
112
113 1
        return $this;
114
    }
115
116
    /**
117
     * Specifies the up or down angle of the camera relative to the Street View vehicle.
118
     * This is often, but not always, flat horizontal.
119
     * Positive values angle the camera up (with 90 degrees indicating straight up);
120
     * negative values angle the camera down (with -90 indicating straight down).
121
     *
122
     * @param int $cameraPitch
123
     * @return $this
124
     * @throws UnexpectedValueException
125
     */
126 3
    public function setCameraPitch(int $cameraPitch):self
127
    {
128 3
        if ($cameraPitch > 90) {
129 1
            throw new UnexpectedValueException(
130 1
                'Camera pitch value for Google Street View cannot exceed 90 degrees.'
131
            );
132
        }
133 2
        if ($cameraPitch < -90) {
134 1
            throw new UnexpectedValueException(
135 1
                'Camera pitch value for Google Street View cannot be inferior of -90 degrees.'
136
            );
137
        }
138
139 1
        $this->cameraPitch = $cameraPitch;
140
141 1
        return $this;
142
    }
143
144
    /**
145
     * Sets a radius, specified in meters,
146
     * in which to search for a panorama, centered on the given latitude and longitude.
147
     * Valid values are non-negative integers.
148
     *
149
     * @param int $radius
150
     * @return $this
151
     * @throws UnexpectedValueException
152
     */
153 2
    public function setRadius(int $radius): self
154
    {
155 2
        if ($radius < 0) {
156 1
            throw new UnexpectedValueException(
157 1
                'Radius value cannot be negative.'
158
            );
159
        }
160
161 1
        $this->radius = $radius;
162
163 1
        return $this;
164
    }
165
166
    /**
167
     * Indicates the compass heading of the camera.
168
     * Accepted values are from 0 to 360 (both values indicating North, with 90 indicating East, and 180 South).
169
     *
170
     * @param int $heading
171
     * @return $this
172
     * @throws UnexpectedValueException
173
     */
174 4
    public function setHeading(int $heading): self
175
    {
176 4
        if ($heading < 0) {
177 1
            throw new UnexpectedValueException(
178 1
                'Heading value cannot be inferior to zero degree.'
179
            );
180
        }
181 3
        if ($heading > 360) {
182 1
            throw new UnexpectedValueException(
183 1
                'Heading value cannot exceed 360 degrees.'
184
            );
185
        }
186
187 2
        $this->heading = $heading;
188
189 2
        return $this;
190
    }
191
192
    /**
193
     * Limits Street View searches to selected sources. Valid values are:
194
     *  - default uses the default sources for Street View; searches are not limited to specific sources.
195
     *  - outdoor limits searches to outdoor collections.
196
     *
197
     * @param string $source
198
     * @return $this
199
     * @throws UnexpectedValueException
200
     */
201 2
    public function setSource(string $source): self
202
    {
203 2
        if (!in_array($source, [self::SOURCE_DEFAULT, self::SOURCE_OUTDOOR], true)) {
204 1
            throw new UnexpectedValueException(sprintf(
205 1
                'Source value "%s" is unknown, only "%s" or "%s" values expected.',
206 1
                $source, self::SOURCE_DEFAULT, self::SOURCE_OUTDOOR
207
            ));
208
        }
209
210 1
        $this->source = $source;
211
212 1
        return $this;
213
    }
214
215
    /**
216
     * @param int $height
217
     * @return $this
218
     * @throws UnexpectedValueException
219
     */
220 2
    public function setImageHeight(int $height): self
221
    {
222 2
        if ($height < 1) {
223 1
            throw new UnexpectedValueException(
224 1
                'Image height value cannot be negative or equal to zero.'
225
            );
226
        }
227
228 1
        $this->imageHeight = $height;
229
230 1
        return $this;
231
    }
232
233
    /**
234
     * @param int $width
235
     * @return $this
236
     * @throws UnexpectedValueException
237
     */
238 2
    public function setImageWidth(int $width): self
239
    {
240 2
        if ($width < 1) {
241 1
            throw new UnexpectedValueException(
242 1
                'Image height value cannot be negative or equal to zero.'
243
            );
244
        }
245
246 1
        $this->imageWidth = $width;
247
248 1
        return $this;
249
    }
250
251
    /**
252
     * Returns URL to a static (non-interactive) Street View panorama or thumbnail.
253
     *
254
     * @param string $location
255
     * @return string
256
     * @throws BadStatusCodeException
257
     */
258 2
    public function getImageUrlByLocation(string $location): string
259
    {
260 2
        $parameters = $this->getRequestParameters([
261 2
            'location' => $location
262
        ]);
263
264 2
        return $this->getImageUrl($parameters);
265
    }
266
267
    /**
268
     * Returns URL to a static (non-interactive) Street View panorama or thumbnail
269
     *
270
     * @param float $latitude
271
     * @param float $longitude
272
     * @return string
273
     * @throws BadStatusCodeException
274
     */
275 1
    public function getImageUrlByLatitudeAndLongitude(float $latitude, float $longitude): string
276
    {
277 1
        $parameters = $this->getRequestParameters([
278 1
            'location' => $latitude . ',' . $longitude
279
        ]);
280
281 1
        return $this->getImageUrl($parameters);
282
    }
283
284
    /**
285
     * Returns URL to a static (non-interactive) Street View panorama or thumbnail
286
     *
287
     * @param string $panoramaId
288
     * @return string
289
     * @throws BadStatusCodeException
290
     */
291 1
    public function getImageUrlByPanoramaId(string $panoramaId): string
292
    {
293 1
        $parameters = $this->getRequestParameters([
294 1
            'pano' => $panoramaId
295
        ]);
296
297 1
        return $this->getImageUrl($parameters);
298
    }
299
300
    /**
301
     * Returns URL to a static (non-interactive) Street View panorama or thumbnail
302
     * The viewport is defined with URL parameters sent through a standard HTTP request, and is returned as a static image.
303
     *
304
     * @param array $parameters
305
     * @return string
306
     * @throws BadStatusCodeException
307
     */
308 4
    private function getImageUrl(array $parameters): string
309
    {
310 4
        $uri = $this->endpointImage . '?' . http_build_query($parameters);
311
312 4
        $response = $this->client->get($uri);
313
314 4
        if ($response->getStatusCode() !== 200) {
315 1
            throw new BadStatusCodeException(
316 1
                'Could not connect to ' . $this->endpointImage,
317 1
                $response->getStatusCode()
318
            );
319
        }
320
321 3
        return $uri;
322
    }
323
324
    /**
325
     * Requests provide data about Street View panoramas.
326
     * Using the metadata, you can find out if a Street View image is available at a given location,
327
     * as well as getting programmatic access to the latitude and longitude,
328
     * the panorama ID, the date the photo was taken, and the copyright information for the image.
329
     * Accessing this metadata allows you to customize error behavior in your application.
330
     * Street View API metadata requests are free to use. No quota is consumed when you request metadata.
331
     *
332
     * @param string $location
333
     * @return array
334
     * @throws UnexpectedValueException
335
     * @throws RequestException
336
     * @throws BadStatusCodeException
337
     * @throws UnexpectedStatusException
338
     */
339 11
    public function getMetadata(string $location): array
340
    {
341 11
        $location = trim($location);
342
343 11
        if (empty($location)) {
344 1
            throw new UnexpectedValueException(
345 1
                'Location argument cannot be empty to request Google Street view API Metadata.'
346
            );
347
        }
348
349 10
        $payload = $this->getRequestPayload(compact('location'));
350
351
        try {
352 10
            $response = $this->client->request('GET', $this->endpointMetadata, $payload);
353 1
        } catch (GuzzleException $e) {
354 1
            throw new RequestException(
355 1
                'Guzzle http client request failed.', $e
356
            );
357
        }
358
359 9
        if ($response->getStatusCode() !== 200) {
360 1
            throw new BadStatusCodeException(
361 1
                'Could not connect to ' . $this->endpointMetadata,
362 1
                $response->getStatusCode()
363
            );
364
        }
365
366 8
        $response = json_decode($response->getBody());
367
368
        // Indicates that no panorama could be found near the provided location.
369
        // Indicates that no errors occurred; a panorama is found and metadata is returned.
370 8
        if ($response->status === 'OK') {
371 1
            return $this->formatMetadataResponse($response);
372
        }
373
374 7
        $this->handleResponseStatus($response->status);
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
375
    }
376
377 7
    private function handleResponseStatus(string $status)
378
    {
379
        // This may occur if a non-existent or invalid panorama ID is given.
380 7
        if ($status === 'ZERO_RESULTS') {
381 1
            throw new UnexpectedStatusException('Google Street view return zero results.');
382
        }
383
        // Indicates that the address string provided in the location parameter could not be found.
384
        // This may occur if a non-existent address is given.
385 6
        if ($status === 'NOT_FOUND') {
386 1
            throw new UnexpectedStatusException('No Google Street view result found.');
387
        }
388
        // Indicates that you have exceeded your daily quota or per-second quota for this API.
389 5
        if ($status === 'OVER_QUERY_LIMIT') {
390 1
            throw new UnexpectedStatusException('Google Street view API quota exceed.');
391
        }
392
        // Indicates that your request was denied.
393
        // This may occur if you did not use an API key or client ID, or
394
        // if the Street View API is not activated in the Google Cloud Platform Console project containing your API key.
395 4
        if ($status === 'REQUEST_DENIED') {
396 1
            throw new UnexpectedStatusException('Google Street view denied the request.');
397
        }
398
        // Generally indicates that the query parameters (address or latlng or components) are missing.
399 3
        if ($status === 'INVALID_REQUEST') {
400 1
            throw new UnexpectedStatusException('Google Street view request is invalid.');
401
        }
402
        // Indicates that the request could not be processed due to a server error.
403
        // This is often a temporary status. The request may succeed if you try again.
404 2
        if ($status === 'UNKNOWN_ERROR') {
405 1
            throw new UnexpectedStatusException('Google Street view unknown error occurred. Please try again.');
406
        }
407
408 1
        throw new UnexpectedStatusException(
409 1
            'Google Street view respond an unknown status response : "' . $status . '".'
410
        );
411
    }
412
413 1
    protected function formatMetadataResponse($response): array
414
    {
415
        return [
416 1
            'lat'           => $response->location->lat,
417 1
            'lng'           => $response->location->lng,
418 1
            'date'          => $response->date,
419 1
            'copyright'     => $response->copyright,
420 1
            'panoramaId'    => $response->pano_id
421
        ];
422
    }
423
424 10
    private function getRequestPayload(array $parameters): array
425
    {
426 10
        return ['query' => $this->getRequestParameters($parameters)];
427
    }
428
429 14
    private function getRequestParameters(array $parameters): array
430
    {
431
        $defaultParameters = [
432 14
            'key'       => $this->apiKey,
433 14
            'size'      => $this->imageWidth . 'x' . $this->imageHeight,
434 14
            'fov'       => $this->cameraFov,
435 14
            'pitch'     => $this->cameraPitch,
436 14
            'radius'    => $this->radius,
437 14
            'source'    => $this->source
438
        ];
439
440
        // optional parameters which have not default value
441 14
        if ($this->heading) {
442 1
            $defaultParameters['heading'] = $this->heading;
443
        }
444 14
        if ($this->signature) {
445 1
            $defaultParameters['signature'] = $this->signature;
446
        }
447
448 14
        return array_merge($defaultParameters, $parameters);
449
    }
450
451
}
452