1
|
|
|
<?php |
2
|
|
|
namespace Germania\GeoData; |
3
|
|
|
|
4
|
|
|
use GuzzleHttp\Client as GuzzleClient; |
5
|
|
|
use GuzzleHttp\Exception\RequestException; |
6
|
|
|
use GuzzleHttp\Exception\ClientException; |
7
|
|
|
use GuzzleHttp\Psr7; |
8
|
|
|
use Germania\GeoData\GeoData; |
9
|
|
|
use Germania\GeoData\GeoDataFactory; |
10
|
|
|
use Psr\Log\LoggerInterface; |
11
|
|
|
use Psr\Log\LoggerAwareInterface; |
12
|
|
|
use Psr\Log\LoggerAwareTrait; |
13
|
|
|
use Psr\Log\NullLogger; |
14
|
|
|
|
15
|
|
|
|
16
|
|
|
class GuzzleGeoDataFactory implements LoggerAwareInterface |
17
|
|
|
{ |
18
|
|
|
|
19
|
|
|
use LoggerAwareTrait; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var \GuzzleHttp\Client |
23
|
|
|
*/ |
24
|
|
|
public $http_client; |
25
|
|
|
|
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var string |
29
|
|
|
*/ |
30
|
|
|
public $url_path = "coordinates"; |
31
|
|
|
|
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var GeoDataFactory |
35
|
|
|
*/ |
36
|
|
|
public $geodata_factory; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var string |
40
|
|
|
*/ |
41
|
|
|
public $request_exception_loglevel = "error"; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var string |
45
|
|
|
*/ |
46
|
|
|
public $client_exception_loglevel = "error"; |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @param GuzzleClient $http_client Guzzle Client, configured for Germania's GeoCoder API |
51
|
|
|
* @param LoggerInterface $logger PSR-3 Logger |
52
|
|
|
*/ |
53
|
24 |
|
public function __construct(GuzzleClient $http_client, LoggerInterface $logger = null) |
54
|
|
|
{ |
55
|
24 |
|
$this->http_client = $http_client; |
56
|
24 |
|
$this->geodata_factory = new GeoDataFactory; |
57
|
24 |
|
$this->setLogger($logger ?: new NullLogger); |
58
|
24 |
|
} |
59
|
|
|
|
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @param string $loglevel PSR-3 Loglevel name |
63
|
|
|
*/ |
64
|
2 |
|
public function setRequestExceptionLoglevel( string $loglevel ) |
65
|
|
|
{ |
66
|
2 |
|
$this->request_exception_loglevel = $loglevel; |
67
|
2 |
|
return $this; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @param string $loglevel PSR-3 Loglevel name |
72
|
|
|
*/ |
73
|
2 |
|
public function setClientRxceptionLoglevel( string $loglevel ) |
74
|
|
|
{ |
75
|
2 |
|
$this->client_exception_loglevel = $loglevel; |
76
|
2 |
|
return $this; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* @param string $location [description] |
82
|
|
|
* @return GeoData |
83
|
|
|
* @throws \RuntimeException |
84
|
|
|
*/ |
85
|
22 |
|
public function fromString( string $location ) : GeoData |
86
|
|
|
{ |
87
|
|
|
try { |
88
|
|
|
// Guzzle client returns ResponseInterface! |
89
|
22 |
|
$response = $this->http_client->get( $this->url_path, [ |
90
|
22 |
|
"query" => ['search' => $location] |
91
|
|
|
]); |
92
|
|
|
} |
93
|
8 |
|
catch (ClientException $e) { |
94
|
6 |
|
$e_response = $e->getResponse(); |
95
|
6 |
|
$e_status = $e_response->getStatusCode(); |
96
|
|
|
|
97
|
6 |
|
$msg = sprintf("Client-side error %s on Geocoder API request: %s", $e_status, $e->getMessage()); |
98
|
6 |
|
$this->logger->log( $this->client_exception_loglevel, $msg, [ |
99
|
6 |
|
'exception' => get_class($e) |
100
|
|
|
]); |
101
|
|
|
|
102
|
|
|
switch ($e_status): |
103
|
6 |
|
case 404: |
104
|
4 |
|
throw new GeoDataFactoryNotFoundException($msg, 0, $e); |
105
|
|
|
break; |
|
|
|
|
106
|
|
|
default: |
107
|
2 |
|
throw new GeoDataFactoryRuntimeException($msg, 0, $e); |
108
|
|
|
break; |
|
|
|
|
109
|
|
|
endswitch; |
110
|
|
|
} |
111
|
2 |
|
catch (RequestException $e) { |
112
|
2 |
|
$msg = sprintf("Request-related error on Geocoder API request: %s", $e->getMessage()); |
113
|
2 |
|
$this->logger->log( $this->request_exception_loglevel, $msg, [ |
114
|
2 |
|
'exception' => get_class($e) |
115
|
|
|
]); |
116
|
2 |
|
throw new GeoDataFactoryRuntimeException($msg, 0, $e); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
|
120
|
|
|
|
121
|
|
|
try { |
122
|
14 |
|
$response_body = $response->getBody(); |
123
|
14 |
|
$response_body_decoded = json_decode($response_body, "associative"); |
124
|
14 |
|
$this->validateDecodedResponse( $response_body_decoded ); |
125
|
|
|
} |
126
|
12 |
|
catch (\Throwable $e) { |
127
|
12 |
|
$msg = sprintf("Error on Geocoder API response validation: %s", $e->getMessage()); |
128
|
12 |
|
$this->logger->log( "error", $msg, [ |
129
|
12 |
|
'exception' => get_class($e) |
130
|
|
|
]); |
131
|
12 |
|
throw new GeoDataFactoryRuntimeException($msg, 0, $e); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
|
135
|
2 |
|
$coordinates_raw = $response_body_decoded['data']["attributes"]; |
136
|
2 |
|
$geodata = $this->geodata_factory->fromArray( $coordinates_raw ); |
137
|
|
|
|
138
|
|
|
|
139
|
2 |
|
return $geodata; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
|
143
|
|
|
|
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Validates the decoded response, throwing things in error case. |
147
|
|
|
* |
148
|
|
|
* @param mixed $response_body_decoded |
149
|
|
|
* @return void |
150
|
|
|
* |
151
|
|
|
* @throws UnexpectedValueException |
152
|
|
|
*/ |
153
|
14 |
|
protected function validateDecodedResponse( $response_body_decoded ) |
154
|
|
|
{ |
155
|
|
|
// "data" is quite common in JsonAPI responses, |
156
|
|
|
// however, we need it as array. |
157
|
|
|
|
158
|
14 |
|
if (!is_array( $response_body_decoded )): |
159
|
4 |
|
throw new \UnexpectedValueException("GeocoderAPI response: Expected array"); |
160
|
|
|
endif; |
161
|
|
|
|
162
|
10 |
|
if (!isset( $response_body_decoded['data'] )): |
163
|
2 |
|
throw new \UnexpectedValueException("GeocoderAPI response: Missing 'data' element"); |
164
|
|
|
endif; |
165
|
|
|
|
166
|
8 |
|
if (!is_array( $response_body_decoded['data'] )): |
167
|
2 |
|
throw new \UnexpectedValueException("GeocoderAPI response: Element 'data' is not array"); |
168
|
|
|
endif; |
169
|
|
|
|
170
|
6 |
|
if (!isset( $response_body_decoded['data']["attributes"] )): |
171
|
2 |
|
throw new \UnexpectedValueException("GeocoderAPI response: Missing 'data.attributes' element"); |
172
|
|
|
endif; |
173
|
|
|
|
174
|
4 |
|
if (!is_array( $response_body_decoded['data']["attributes"] )): |
175
|
2 |
|
throw new \UnexpectedValueException("GeocoderAPI response: Element 'data.attributes' is not array"); |
176
|
|
|
endif; |
177
|
|
|
|
178
|
|
|
} |
179
|
|
|
} |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.